diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java index cb2c54349c6d0..f520f6a9ef492 100644 --- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java @@ -17,21 +17,32 @@ package com.android.server.appop; import static android.app.AppOpsManager.MODE_ALLOWED; -import static android.app.AppOpsManager.MODE_FOREGROUND; +import static android.app.AppOpsManager.OP_NONE; import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM; +import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES; +import static android.app.AppOpsManager.opRestrictsRead; import static android.app.AppOpsManager.opToDefaultMode; +import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS; + +import android.Manifest; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.AppOpsManager.Mode; import android.content.Context; +import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.UserPackage; import android.os.AsyncTask; +import android.os.Binder; import android.os.Handler; +import android.os.RemoteException; import android.os.UserHandle; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.AtomicFile; import android.util.Slog; import android.util.SparseArray; @@ -42,12 +53,15 @@ import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.XmlUtils; +import com.android.internal.util.function.pooled.PooledLambda; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.LocalServices; import com.android.server.pm.UserManagerInternal; import com.android.server.pm.permission.PermissionManagerServiceInternal; +import libcore.util.EmptyArray; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -56,8 +70,11 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Objects; /** @@ -111,6 +128,10 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface @GuardedBy("mLock") final SparseArray> mUserPackageModes = new SparseArray<>(); + final SparseArray> mOpModeWatchers = new SparseArray<>(); + final ArrayMap> mPackageModeWatchers = + new ArrayMap<>(); + final AtomicFile mFile; final Runnable mWriteRunner = new Runnable() { public void run() { @@ -132,6 +153,10 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface boolean mWriteScheduled; boolean mFastWriteScheduled; + + // Constant meaning that any UID should be matched when dispatching callbacks + private static final int UID_ANY = -2; + AppOpsCheckingServiceImpl(File storageFile, @NonNull Object lock, Handler handler, Context context, SparseArray switchedOps) { @@ -326,43 +351,348 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface } @Override - public SparseBooleanArray getForegroundOps(int uid) { - SparseBooleanArray result = new SparseBooleanArray(); + public void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, + int op) { + Objects.requireNonNull(changedListener); synchronized (mLock) { - SparseIntArray modes = mUidModes.get(uid); - if (modes == null) { - return result; - } - for (int i = 0; i < modes.size(); i++) { - if (modes.valueAt(i) == MODE_FOREGROUND) { - result.put(modes.keyAt(i), true); - } + ArraySet modeWatcherSet = mOpModeWatchers.get(op); + if (modeWatcherSet == null) { + modeWatcherSet = new ArraySet<>(); + mOpModeWatchers.put(op, modeWatcherSet); } + modeWatcherSet.add(changedListener); } - - return result; } @Override - public SparseBooleanArray getForegroundOps(String packageName, int userId) { - SparseBooleanArray result = new SparseBooleanArray(); + public void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener, + @NonNull String packageName) { + Objects.requireNonNull(changedListener); + Objects.requireNonNull(packageName); synchronized (mLock) { - ArrayMap packageModes = mUserPackageModes.get(userId); - if (packageModes == null) { - return result; + ArraySet modeWatcherSet = + mPackageModeWatchers.get(packageName); + if (modeWatcherSet == null) { + modeWatcherSet = new ArraySet<>(); + mPackageModeWatchers.put(packageName, modeWatcherSet); } - SparseIntArray modes = packageModes.get(packageName); - if (modes == null) { - return result; + modeWatcherSet.add(changedListener); + } + } + + @Override + public void removeListener(@NonNull OnOpModeChangedListener changedListener) { + Objects.requireNonNull(changedListener); + + synchronized (mLock) { + for (int i = mOpModeWatchers.size() - 1; i >= 0; i--) { + ArraySet cbs = mOpModeWatchers.valueAt(i); + cbs.remove(changedListener); + if (cbs.size() <= 0) { + mOpModeWatchers.removeAt(i); + } } - for (int i = 0; i < modes.size(); i++) { - if (modes.valueAt(i) == MODE_FOREGROUND) { - result.put(modes.keyAt(i), true); + + for (int i = mPackageModeWatchers.size() - 1; i >= 0; i--) { + ArraySet cbs = mPackageModeWatchers.valueAt(i); + cbs.remove(changedListener); + if (cbs.size() <= 0) { + mPackageModeWatchers.removeAt(i); + } + } + } + } + + @Override + public ArraySet getOpModeChangedListeners(int op) { + synchronized (mLock) { + ArraySet modeChangedListenersSet = mOpModeWatchers.get(op); + if (modeChangedListenersSet == null) { + return new ArraySet<>(); + } + return new ArraySet<>(modeChangedListenersSet); + } + } + + @Override + public ArraySet getPackageModeChangedListeners( + @NonNull String packageName) { + Objects.requireNonNull(packageName); + + synchronized (mLock) { + ArraySet modeChangedListenersSet = + mPackageModeWatchers.get(packageName); + if (modeChangedListenersSet == null) { + return new ArraySet<>(); + } + return new ArraySet<>(modeChangedListenersSet); + } + } + + @Override + public void notifyWatchersOfChange(int code, int uid) { + ArraySet listenerSet = getOpModeChangedListeners(code); + if (listenerSet == null) { + return; + } + for (int i = 0; i < listenerSet.size(); i++) { + final OnOpModeChangedListener listener = listenerSet.valueAt(i); + notifyOpChanged(listener, code, uid, null); + } + } + + @Override + public void notifyOpChanged(@NonNull OnOpModeChangedListener onModeChangedListener, int code, + int uid, @Nullable String packageName) { + Objects.requireNonNull(onModeChangedListener); + + if (uid != UID_ANY && onModeChangedListener.getWatchingUid() >= 0 + && onModeChangedListener.getWatchingUid() != uid) { + return; + } + + // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE + int[] switchedCodes; + if (onModeChangedListener.getWatchedOpCode() == ALL_OPS) { + switchedCodes = mSwitchedOps.get(code); + } else if (onModeChangedListener.getWatchedOpCode() == OP_NONE) { + switchedCodes = new int[]{code}; + } else { + switchedCodes = new int[]{onModeChangedListener.getWatchedOpCode()}; + } + + for (int switchedCode : 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. + final long identity = Binder.clearCallingIdentity(); + try { + if (shouldIgnoreCallback(switchedCode, onModeChangedListener.getCallingPid(), + onModeChangedListener.getCallingUid())) { + continue; + } + onModeChangedListener.onOpModeChanged(switchedCode, uid, packageName); + } catch (RemoteException e) { + /* ignore */ + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) { + // 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 opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS, + watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED; + } + + @Override + public void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground, + @Nullable OnOpModeChangedListener callbackToIgnore) { + String[] uidPackageNames = getPackagesForUid(uid); + ArrayMap> callbackSpecs = null; + + synchronized (mLock) { + ArraySet callbacks = mOpModeWatchers.get(code); + if (callbacks != null) { + final int callbackCount = callbacks.size(); + for (int i = 0; i < callbackCount; i++) { + OnOpModeChangedListener callback = callbacks.valueAt(i); + + if (onlyForeground && (callback.getFlags() + & WATCH_FOREGROUND_CHANGES) == 0) { + continue; + } + + ArraySet changedPackages = new ArraySet<>(); + Collections.addAll(changedPackages, uidPackageNames); + if (callbackSpecs == null) { + callbackSpecs = new ArrayMap<>(); + } + callbackSpecs.put(callback, changedPackages); + } + } + + for (String uidPackageName : uidPackageNames) { + callbacks = mPackageModeWatchers.get(uidPackageName); + if (callbacks != null) { + if (callbackSpecs == null) { + callbackSpecs = new ArrayMap<>(); + } + final int callbackCount = callbacks.size(); + for (int i = 0; i < callbackCount; i++) { + OnOpModeChangedListener callback = callbacks.valueAt(i); + + if (onlyForeground && (callback.getFlags() + & WATCH_FOREGROUND_CHANGES) == 0) { + continue; + } + + ArraySet changedPackages = callbackSpecs.get(callback); + if (changedPackages == null) { + changedPackages = new ArraySet<>(); + callbackSpecs.put(callback, changedPackages); + } + changedPackages.add(uidPackageName); + } + } + } + + if (callbackSpecs != null && callbackToIgnore != null) { + callbackSpecs.remove(callbackToIgnore); + } + } + + if (callbackSpecs == null) { + return; + } + + for (int i = 0; i < callbackSpecs.size(); i++) { + final OnOpModeChangedListener callback = callbackSpecs.keyAt(i); + final ArraySet reportedPackageNames = callbackSpecs.valueAt(i); + if (reportedPackageNames == null) { + mHandler.sendMessage(PooledLambda.obtainMessage( + AppOpsCheckingServiceImpl::notifyOpChanged, + this, callback, code, uid, (String) null)); + + } else { + final int reportedPackageCount = reportedPackageNames.size(); + for (int j = 0; j < reportedPackageCount; j++) { + final String reportedPackageName = reportedPackageNames.valueAt(j); + mHandler.sendMessage(PooledLambda.obtainMessage( + AppOpsCheckingServiceImpl::notifyOpChanged, + this, callback, code, uid, reportedPackageName)); + } + } + } + } + + private static String[] getPackagesForUid(int uid) { + String[] packageNames = null; + + // Very early during boot the package manager is not yet or not yet fully started. At this + // time there are no packages yet. + if (AppGlobals.getPackageManager() != null) { + try { + packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid); + } catch (RemoteException e) { + /* ignore - local call */ + } + } + if (packageNames == null) { + return EmptyArray.STRING; + } + return packageNames; + } + + @Override + public SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps) { + synchronized (mLock) { + return evalForegroundOps(mUidModes.get(uid), foregroundOps); + } + } + + @Override + public SparseBooleanArray evalForegroundPackageOps(String packageName, + SparseBooleanArray foregroundOps, @UserIdInt int userId) { + synchronized (mLock) { + ArrayMap packageModes = mUserPackageModes.get(userId, null); + return evalForegroundOps(packageModes == null ? null : packageModes.get(packageName), + foregroundOps); + } + } + + private SparseBooleanArray evalForegroundOps(SparseIntArray opModes, + SparseBooleanArray foregroundOps) { + SparseBooleanArray tempForegroundOps = foregroundOps; + if (opModes != null) { + for (int i = opModes.size() - 1; i >= 0; i--) { + if (opModes.valueAt(i) == AppOpsManager.MODE_FOREGROUND) { + if (tempForegroundOps == null) { + tempForegroundOps = new SparseBooleanArray(); + } + evalForegroundWatchers(opModes.keyAt(i), tempForegroundOps); + } + } + } + return tempForegroundOps; + } + + private void evalForegroundWatchers(int op, SparseBooleanArray foregroundOps) { + boolean curValue = foregroundOps.get(op, false); + ArraySet listenerSet = mOpModeWatchers.get(op); + if (listenerSet != null) { + for (int cbi = listenerSet.size() - 1; !curValue && cbi >= 0; cbi--) { + if ((listenerSet.valueAt(cbi).getFlags() + & AppOpsManager.WATCH_FOREGROUND_CHANGES) != 0) { + curValue = true; + } + } + } + foregroundOps.put(op, curValue); + } + + @Override + public boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, + PrintWriter printWriter) { + boolean needSep = false; + if (mOpModeWatchers.size() > 0) { + boolean printedHeader = false; + for (int i = 0; i < mOpModeWatchers.size(); i++) { + if (dumpOp >= 0 && dumpOp != mOpModeWatchers.keyAt(i)) { + continue; + } + boolean printedOpHeader = false; + ArraySet modeChangedListenerSet = + mOpModeWatchers.valueAt(i); + for (int j = 0; j < modeChangedListenerSet.size(); j++) { + final OnOpModeChangedListener listener = modeChangedListenerSet.valueAt(j); + if (dumpPackage != null + && dumpUid != UserHandle.getAppId(listener.getWatchingUid())) { + continue; + } + needSep = true; + if (!printedHeader) { + printWriter.println(" Op mode watchers:"); + printedHeader = true; + } + if (!printedOpHeader) { + printWriter.print(" Op "); + printWriter.print(AppOpsManager.opToName(mOpModeWatchers.keyAt(i))); + printWriter.println(":"); + printedOpHeader = true; + } + printWriter.print(" #"); printWriter.print(j); printWriter.print(": "); + printWriter.println(listener.toString()); } } } - return result; + if (mPackageModeWatchers.size() > 0 && dumpOp < 0) { + boolean printedHeader = false; + for (int i = 0; i < mPackageModeWatchers.size(); i++) { + if (dumpPackage != null + && !dumpPackage.equals(mPackageModeWatchers.keyAt(i))) { + continue; + } + needSep = true; + if (!printedHeader) { + printWriter.println(" Package mode watchers:"); + printedHeader = true; + } + printWriter.print(" Pkg "); printWriter.print(mPackageModeWatchers.keyAt(i)); + printWriter.println(":"); + ArraySet modeChangedListenerSet = + mPackageModeWatchers.valueAt(i); + + for (int j = 0; j < modeChangedListenerSet.size(); j++) { + printWriter.print(" #"); printWriter.print(j); printWriter.print(": "); + printWriter.println(modeChangedListenerSet.valueAt(j).toString()); + } + } + } + return needSep; } private void scheduleWriteLocked() { diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java index 76f1f8ada8304..90968983d4d7f 100644 --- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java +++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java @@ -16,13 +16,17 @@ package com.android.server.appop; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.AppOpsManager.Mode; +import android.util.ArraySet; import android.util.SparseBooleanArray; import android.util.SparseIntArray; import com.android.internal.annotations.VisibleForTesting; +import java.io.PrintWriter; + /** * Interface for accessing and modifying modes for app-ops i.e. package and uid modes. * This interface also includes functions for added and removing op mode watchers. @@ -144,18 +148,99 @@ public interface AppOpsCheckingServiceInterface { void clearAllModes(); /** - * @param uid UID to query foreground ops for. - * @return SparseBooleanArray where the keys are the op codes for which their modes are - * MODE_FOREGROUND for the passed UID. + * Registers changedListener to listen to op's mode change. + * @param changedListener the listener that must be trigger on the op's mode change. + * @param op op representing the app-op whose mode change needs to be listened to. */ - SparseBooleanArray getForegroundOps(int uid); + void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, int op); /** - * - * @param packageName Package name to check for. - * @param userId User ID to check for. - * @return SparseBooleanArray where the keys are the op codes for which their modes are - * MODE_FOREGROUND for the passed package name and user ID. + * Registers changedListener to listen to package's app-op's mode change. + * @param changedListener the listener that must be trigger on the mode change. + * @param packageName of the package whose app-op's mode change needs to be listened to. */ - SparseBooleanArray getForegroundOps(String packageName, int userId); + void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener, + @NonNull String packageName); + + /** + * Stop the changedListener from triggering on any mode change. + * @param changedListener the listener that needs to be removed. + */ + void removeListener(@NonNull OnOpModeChangedListener changedListener); + + /** + * Temporary API which will be removed once we can safely untangle the methods that use this. + * Returns a set of OnOpModeChangedListener that are listening for op's mode changes. + * @param op app-op whose mode change is being listened to. + */ + ArraySet getOpModeChangedListeners(int op); + + /** + * Temporary API which will be removed once we can safely untangle the methods that use this. + * Returns a set of OnOpModeChangedListener that are listening for package's op's mode changes. + * @param packageName of package whose app-op's mode change is being listened to. + */ + ArraySet getPackageModeChangedListeners(@NonNull String packageName); + + /** + * Temporary API which will be removed once we can safely untangle the methods that use this. + * Notify that the app-op's mode is changed by triggering the change listener. + * @param op App-op whose mode has changed + * @param uid user id associated with the app-op (or, if UID_ANY, notifies all users) + */ + void notifyWatchersOfChange(int op, int uid); + + /** + * Temporary API which will be removed once we can safely untangle the methods that use this. + * Notify that the app-op's mode is changed by triggering the change listener. + * @param changedListener the change listener. + * @param op App-op whose mode has changed + * @param uid user id associated with the app-op + * @param packageName package name that is associated with the app-op + */ + void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid, + @Nullable String packageName); + + /** + * Temporary API which will be removed once we can safely untangle the methods that use this. + * Notify that the app-op's mode is changed to all packages associated with the uid by + * triggering the appropriate change listener. + * @param op App-op whose mode has changed + * @param uid user id associated with the app-op + * @param onlyForeground true if only watchers that + * @param callbackToIgnore callback that should be ignored. + */ + void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground, + @Nullable OnOpModeChangedListener callbackToIgnore); + + /** + * TODO: Move hasForegroundWatchers and foregroundOps into this. + * Go over the list of app-ops for the uid and mark app-ops with MODE_FOREGROUND in + * foregroundOps. + * @param uid for which the app-op's mode needs to be marked. + * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true. + * @return foregroundOps. + */ + SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps); + + /** + * Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in + * foregroundOps. + * @param packageName for which the app-op's mode needs to be marked. + * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true. + * @param userId user id associated with the package. + * @return foregroundOps. + */ + SparseBooleanArray evalForegroundPackageOps(String packageName, + SparseBooleanArray foregroundOps, @UserIdInt int userId); + + /** + * Dump op mode and package mode listeners and their details. + * @param dumpOp if -1 then op mode listeners for all app-ops are dumped. If it's set to an + * app-op, only the watchers for that app-op are dumped. + * @param dumpUid uid for which we want to dump op mode watchers. + * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name. + * @param printWriter writer to dump to. + */ + boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter); } diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java index 32dcb5aff8ea6..0094b86975bbe 100644 --- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java +++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java @@ -17,10 +17,14 @@ package com.android.server.appop; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.ArraySet; import android.util.Log; import android.util.SparseBooleanArray; import android.util.SparseIntArray; +import java.io.PrintWriter; + /** * Logging decorator for {@link AppOpsCheckingServiceInterface}. */ @@ -130,15 +134,83 @@ public class AppOpsCheckingServiceLoggingDecorator implements AppOpsCheckingServ } @Override - public SparseBooleanArray getForegroundOps(int uid) { - Log.i(LOG_TAG, "getForegroundOps(uid = " + uid + ")"); - return mService.getForegroundOps(uid); + public void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, + int op) { + Log.i(LOG_TAG, "startWatchingOpModeChanged(changedListener = " + changedListener + ", op = " + + op + ")"); + mService.startWatchingOpModeChanged(changedListener, op); } @Override - public SparseBooleanArray getForegroundOps(String packageName, int userId) { - Log.i(LOG_TAG, "getForegroundOps(packageName = " + packageName + ", userId = " + userId + public void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener, + @NonNull String packageName) { + Log.i(LOG_TAG, "startWatchingPackageModeChanged(changedListener = " + changedListener + + ", packageName = " + packageName + ")"); + mService.startWatchingPackageModeChanged(changedListener, packageName); + } + + @Override + public void removeListener(@NonNull OnOpModeChangedListener changedListener) { + Log.i(LOG_TAG, "removeListener(changedListener = " + changedListener + ")"); + mService.removeListener(changedListener); + } + + @Override + public ArraySet getOpModeChangedListeners(int op) { + Log.i(LOG_TAG, "getOpModeChangedListeners(op = " + op + ")"); + return mService.getOpModeChangedListeners(op); + } + + @Override + public ArraySet getPackageModeChangedListeners( + @NonNull String packageName) { + Log.i(LOG_TAG, "getPackageModeChangedListeners(packageName = " + packageName + ")"); + return mService.getPackageModeChangedListeners(packageName); + } + + @Override + public void notifyWatchersOfChange(int op, int uid) { + Log.i(LOG_TAG, "notifyWatchersOfChange(op = " + op + ", uid = " + uid + ")"); + mService.notifyWatchersOfChange(op, uid); + } + + @Override + public void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid, + @Nullable String packageName) { + Log.i(LOG_TAG, "notifyOpChanged(changedListener = " + changedListener + ", op = " + op + + ", uid = " + uid + ", packageName = " + packageName + ")"); + mService.notifyOpChanged(changedListener, op, uid, packageName); + } + + @Override + public void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground, + @Nullable OnOpModeChangedListener callbackToIgnore) { + Log.i(LOG_TAG, "notifyOpChangedForAllPkgsInUid(op = " + op + ", uid = " + uid + + ", onlyForeground = " + onlyForeground + ", callbackToIgnore = " + + callbackToIgnore + ")"); + mService.notifyOpChangedForAllPkgsInUid(op, uid, onlyForeground, callbackToIgnore); + } + + @Override + public SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps) { + Log.i(LOG_TAG, "evalForegroundUidOps(uid = " + uid + ", foregroundOps = " + foregroundOps + ")"); - return mService.getForegroundOps(packageName, userId); + return mService.evalForegroundUidOps(uid, foregroundOps); + } + + @Override + public SparseBooleanArray evalForegroundPackageOps(String packageName, + SparseBooleanArray foregroundOps, int userId) { + Log.i(LOG_TAG, "evalForegroundPackageOps(packageName = " + packageName + + ", foregroundOps = " + foregroundOps + ", userId = " + userId + ")"); + return mService.evalForegroundPackageOps(packageName, foregroundOps, userId); + } + + @Override + public boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, + PrintWriter printWriter) { + Log.i(LOG_TAG, "dumpListeners(dumpOp = " + dumpOp + ", dumpUid = " + dumpUid + + ", dumpPackage = " + dumpPackage + ", printWriter = " + printWriter + ")"); + return mService.dumpListeners(dumpOp, dumpUid, dumpPackage, printWriter); } } diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java index 91dbc23246ed8..a028ae16da2f3 100644 --- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java +++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java @@ -17,12 +17,16 @@ package com.android.server.appop; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.AppOpsManager; import android.os.Trace; +import android.util.ArraySet; import android.util.SparseBooleanArray; import android.util.SparseIntArray; +import java.io.PrintWriter; + /** * Surrounds all AppOpsCheckingServiceInterface method calls with Trace.traceBegin and * Trace.traceEnd. These traces are used for performance testing. @@ -201,22 +205,128 @@ public class AppOpsCheckingServiceTracingDecorator implements AppOpsCheckingServ } @Override - public SparseBooleanArray getForegroundOps(int uid) { + public void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, + int op) { Trace.traceBegin(TRACE_TAG, - "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getForegroundOps"); + "TaggedTracingAppOpsCheckingServiceInterfaceImpl#startWatchingOpModeChanged"); try { - return mService.getForegroundOps(uid); + mService.startWatchingOpModeChanged(changedListener, op); } finally { Trace.traceEnd(TRACE_TAG); } } @Override - public SparseBooleanArray getForegroundOps(String packageName, int userId) { + public void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener, + @NonNull String packageName) { Trace.traceBegin(TRACE_TAG, - "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getForegroundOps"); + "TaggedTracingAppOpsCheckingServiceInterfaceImpl#startWatchingPackageModeChanged"); try { - return mService.getForegroundOps(packageName, userId); + mService.startWatchingPackageModeChanged(changedListener, packageName); + } finally { + Trace.traceEnd(TRACE_TAG); + } + } + + @Override + public void removeListener(@NonNull OnOpModeChangedListener changedListener) { + Trace.traceBegin(TRACE_TAG, + "TaggedTracingAppOpsCheckingServiceInterfaceImpl#removeListener"); + try { + mService.removeListener(changedListener); + } finally { + Trace.traceEnd(TRACE_TAG); + } + } + + @Override + public ArraySet getOpModeChangedListeners(int op) { + Trace.traceBegin(TRACE_TAG, + "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getOpModeChangedListeners"); + try { + return mService.getOpModeChangedListeners(op); + } finally { + Trace.traceEnd(TRACE_TAG); + } + } + + @Override + public ArraySet getPackageModeChangedListeners( + @NonNull String packageName) { + Trace.traceBegin(TRACE_TAG, + "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getPackageModeChangedListeners"); + try { + return mService.getPackageModeChangedListeners(packageName); + } finally { + Trace.traceEnd(TRACE_TAG); + } + } + + @Override + public void notifyWatchersOfChange(int op, int uid) { + Trace.traceBegin(TRACE_TAG, + "TaggedTracingAppOpsCheckingServiceInterfaceImpl#notifyWatchersOfChange"); + try { + mService.notifyWatchersOfChange(op, uid); + } finally { + Trace.traceEnd(TRACE_TAG); + } + } + + @Override + public void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid, + @Nullable String packageName) { + Trace.traceBegin(TRACE_TAG, + "TaggedTracingAppOpsCheckingServiceInterfaceImpl#notifyOpChanged"); + try { + mService.notifyOpChanged(changedListener, op, uid, packageName); + } finally { + Trace.traceEnd(TRACE_TAG); + } + } + + @Override + public void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground, + @Nullable OnOpModeChangedListener callbackToIgnore) { + Trace.traceBegin(TRACE_TAG, + "TaggedTracingAppOpsCheckingServiceInterfaceImpl#notifyOpChangedForAllPkgsInUid"); + try { + mService.notifyOpChangedForAllPkgsInUid(op, uid, onlyForeground, callbackToIgnore); + } finally { + Trace.traceEnd(TRACE_TAG); + } + } + + @Override + public SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps) { + Trace.traceBegin(TRACE_TAG, + "TaggedTracingAppOpsCheckingServiceInterfaceImpl#evalForegroundUidOps"); + try { + return mService.evalForegroundUidOps(uid, foregroundOps); + } finally { + Trace.traceEnd(TRACE_TAG); + } + } + + @Override + public SparseBooleanArray evalForegroundPackageOps(String packageName, + SparseBooleanArray foregroundOps, @UserIdInt int userId) { + Trace.traceBegin(TRACE_TAG, + "TaggedTracingAppOpsCheckingServiceInterfaceImpl#evalForegroundPackageOps"); + try { + return mService.evalForegroundPackageOps(packageName, foregroundOps, userId); + } finally { + Trace.traceEnd(TRACE_TAG); + } + } + + @Override + public boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, + PrintWriter printWriter) { + Trace.traceBegin(TRACE_TAG, + "TaggedTracingAppOpsCheckingServiceInterfaceImpl#dumpListeners"); + try { + return mService.dumpListeners(dumpOp, dumpUid, dumpPackage, printWriter); } finally { Trace.traceEnd(TRACE_TAG); } diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictions.java b/services/core/java/com/android/server/appop/AppOpsRestrictions.java index 0241d02f364bd..f7ccd3429f114 100644 --- a/services/core/java/com/android/server/appop/AppOpsRestrictions.java +++ b/services/core/java/com/android/server/appop/AppOpsRestrictions.java @@ -144,11 +144,4 @@ public interface AppOpsRestrictions { */ void dumpRestrictions(PrintWriter printWriter, int dumpOp, String dumpPackage, boolean showUserRestrictions); - - /** - * Listener for when an appop restriction is removed. - */ - interface AppOpsRestrictionRemovedListener { - void onAppOpsRestrictionRemoved(int code); - } } diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java index ae93991d3945a..f51200f2bf0c3 100644 --- a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java @@ -42,8 +42,7 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { private Context mContext; private Handler mHandler; - - private AppOpsRestrictionRemovedListener mAppOpsRestrictionRemovedListener; + private AppOpsCheckingServiceInterface mAppOpsCheckingServiceInterface; // Map from (Object token) to (int code) to (boolean restricted) private final ArrayMap mGlobalRestrictions = new ArrayMap<>(); @@ -57,10 +56,10 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { mUserRestrictionExcludedPackageTags = new ArrayMap<>(); public AppOpsRestrictionsImpl(Context context, Handler handler, - AppOpsRestrictionRemovedListener appOpsRestrictionRemovedListener) { + AppOpsCheckingServiceInterface appOpsCheckingServiceInterface) { mContext = context; mHandler = handler; - mAppOpsRestrictionRemovedListener = appOpsRestrictionRemovedListener; + mAppOpsCheckingServiceInterface = appOpsCheckingServiceInterface; } @Override @@ -212,11 +211,15 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { return allRestrictedCodes; } + // TODO: For clearUserRestrictions, we are calling notifyOpChanged from within the + // LegacyAppOpsServiceInterfaceImpl class. But, for all other changes to restrictions, we're + // calling it from within AppOpsService. This is awkward, and we should probably do it one + // way or the other. private void notifyAllUserRestrictions(SparseBooleanArray allUserRestrictedCodes) { int restrictedCodesSize = allUserRestrictedCodes.size(); for (int j = 0; j < restrictedCodesSize; j++) { int code = allUserRestrictedCodes.keyAt(j); - mHandler.post(() -> mAppOpsRestrictionRemovedListener.onAppOpsRestrictionRemoved(code)); + mHandler.post(() -> mAppOpsCheckingServiceInterface.notifyWatchersOfChange(code, UID_ANY)); } } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 719bb64fad981..a46dd47ad3d83 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -52,7 +52,6 @@ import static android.app.AppOpsManager.SAMPLING_STRATEGY_RARELY_USED; import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM; import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM_OPS; import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE; -import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES; import static android.app.AppOpsManager._NUM_OP; import static android.app.AppOpsManager.extractFlagsFromKey; import static android.app.AppOpsManager.extractUidStateFromKey; @@ -293,11 +292,6 @@ public class AppOpsService extends IAppOpsService.Stub { private final ArrayMap, ArrayList> mUnforwardedAsyncNotedOps = new ArrayMap<>(); - private final SparseArray> mOpModeWatchers = - new SparseArray<>(); - private final ArrayMap> mPackageModeWatchers = - new ArrayMap<>(); - boolean mWriteNoteOpsScheduled; boolean mWriteScheduled; @@ -321,8 +315,6 @@ public class AppOpsService extends IAppOpsService.Stub { @GuardedBy("this") @VisibleForTesting final SparseArray mUidStates = new SparseArray<>(); - @GuardedBy("this") - private boolean mUidStatesInitialized; volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this); @@ -349,6 +341,8 @@ public class AppOpsService extends IAppOpsService.Stub { */ private final SparseArray mSwitchedOps = new SparseArray<>(); + private ActivityManagerInternal mActivityManagerInternal; + /** Package sampled for message collection in the current session */ @GuardedBy("this") private String mSampledPackage = null; @@ -511,6 +505,11 @@ public class AppOpsService extends IAppOpsService.Stub { @NonNull public final ArrayMap pkgOps = new ArrayMap<>(); + // true indicates there is an interested observer, false there isn't but it has such an op + //TODO: Move foregroundOps and hasForegroundWatchers into the AppOpsServiceInterface. + public SparseBooleanArray foregroundOps; + public boolean hasForegroundWatchers; + public UidState(int uid) { this.uid = uid; } @@ -541,6 +540,25 @@ public class AppOpsService extends IAppOpsService.Stub { return getUidStateTracker().evalMode(uid, op, mode); } + public void evalForegroundOps() { + foregroundOps = null; + foregroundOps = mAppOpsCheckingService.evalForegroundUidOps(uid, foregroundOps); + for (int i = pkgOps.size() - 1; i >= 0; i--) { + foregroundOps = mAppOpsCheckingService + .evalForegroundPackageOps(pkgOps.valueAt(i).packageName, foregroundOps, + UserHandle.getUserId(uid)); + } + hasForegroundWatchers = false; + if (foregroundOps != null) { + for (int i = 0; i < foregroundOps.size(); i++) { + if (foregroundOps.valueAt(i)) { + hasForegroundWatchers = true; + break; + } + } + } + } + @SuppressWarnings("GuardedBy") public int getState() { return getUidStateTracker().getUidState(uid); @@ -922,8 +940,7 @@ public class AppOpsService extends IAppOpsService.Stub { storageFile, this, handler, context, mSwitchedOps)); //mAppOpsCheckingService = new AppOpsCheckingServiceLoggingDecorator( // LocalServices.getService(AppOpsCheckingServiceInterface.class)); - mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler, - code -> notifyWatchersOfChange(code, UID_ANY)); + mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler, mAppOpsCheckingService); LockGuard.installLock(this, LockGuard.INDEX_APP_OPS); mStorageFile = new AtomicFile(storageFile, "appops_legacy"); @@ -1091,7 +1108,8 @@ public class AppOpsService extends IAppOpsService.Stub { for (int code : OPS_RESTRICTED_ON_SUSPEND) { ArraySet onModeChangedListeners; synchronized (AppOpsService.this) { - onModeChangedListeners = mOpModeWatchers.get(code); + onModeChangedListeners = + mAppOpsCheckingService.getOpModeChangedListeners(code); if (onModeChangedListeners == null) { continue; } @@ -1131,6 +1149,8 @@ public class AppOpsService extends IAppOpsService.Stub { } } }); + + mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); } @VisibleForTesting @@ -1202,7 +1222,6 @@ public class AppOpsService extends IAppOpsService.Stub { for (int uid : NON_PACKAGE_UIDS) { mUidStates.put(uid, new UidState(uid)); } - mUidStatesInitialized = true; } } @@ -1246,6 +1265,8 @@ public class AppOpsService extends IAppOpsService.Stub { ops.put(code, new Op(uidState, packageName, code, uid)); } } + + uidState.evalForegroundOps(); } /** @@ -1321,40 +1342,12 @@ public class AppOpsService extends IAppOpsService.Stub { synchronized (this) { UidState uidState = getUidStateLocked(uid, false); - boolean hasForegroundWatchers = false; - - for (int i = 0; i < mModeWatchers.size(); i++) { - ModeCallback cb = mModeWatchers.valueAt(i); - if (cb.isWatchingUid(uid) && (cb.getFlags() & WATCH_FOREGROUND_CHANGES) != 0) { - hasForegroundWatchers = true; - break; - } - } - - if (uidState != null && foregroundModeMayChange && hasForegroundWatchers) { - - SparseBooleanArray foregroundOps = new SparseBooleanArray(); - - SparseBooleanArray uidForegroundOps = mAppOpsCheckingService.getForegroundOps(uid); - for (int i = 0; i < uidForegroundOps.size(); i++) { - foregroundOps.put(uidForegroundOps.keyAt(i), true); - } - String[] uidPackageNames = getPackagesForUid(uid); - - int userId = UserHandle.getUserId(uid); - for (String packageName : uidPackageNames) { - SparseBooleanArray packageForegroundOps = - mAppOpsCheckingService.getForegroundOps(packageName, userId); - for (int i = 0; i < packageForegroundOps.size(); i++) { - foregroundOps.put(packageForegroundOps.keyAt(i), true); - } - } - - for (int fgi = foregroundOps.size() - 1; fgi >= 0; fgi--) { - if (!foregroundOps.valueAt(fgi)) { + if (uidState != null && foregroundModeMayChange && uidState.hasForegroundWatchers) { + for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) { + if (!uidState.foregroundOps.valueAt(fgi)) { continue; } - final int code = foregroundOps.keyAt(fgi); + final int code = uidState.foregroundOps.keyAt(fgi); if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code) && uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) { @@ -1363,7 +1356,7 @@ public class AppOpsService extends IAppOpsService.Stub { this, code, uidState.uid, true, null)); } else if (!uidState.pkgOps.isEmpty()) { final ArraySet listenerSet = - mOpModeWatchers.get(code); + mAppOpsCheckingService.getOpModeChangedListeners(code); if (listenerSet != null) { for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) { final OnOpModeChangedListener listener = listenerSet.valueAt(cbi); @@ -1829,6 +1822,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (!uidState.setUidMode(code, mode)) { return; } + uidState.evalForegroundOps(); if (mode != MODE_ERRORED && mode != previousMode) { updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid); } @@ -1847,81 +1841,10 @@ public class AppOpsService extends IAppOpsService.Stub { */ private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground, @Nullable IAppOpsCallback callbackToIgnore) { - String[] uidPackageNames = getPackagesForUid(uid); - ArrayMap> callbackSpecs = null; - synchronized (this) { - ArraySet callbacks = mOpModeWatchers.get(code); - if (callbacks != null) { - final int callbackCount = callbacks.size(); - for (int i = 0; i < callbackCount; i++) { - OnOpModeChangedListener callback = callbacks.valueAt(i); - - if (onlyForeground && (callback.getFlags() - & WATCH_FOREGROUND_CHANGES) == 0) { - continue; - } - - ArraySet changedPackages = new ArraySet<>(); - Collections.addAll(changedPackages, uidPackageNames); - if (callbackSpecs == null) { - callbackSpecs = new ArrayMap<>(); - } - callbackSpecs.put(callback, changedPackages); - } - } - - for (String uidPackageName : uidPackageNames) { - callbacks = mPackageModeWatchers.get(uidPackageName); - if (callbacks != null) { - if (callbackSpecs == null) { - callbackSpecs = new ArrayMap<>(); - } - final int callbackCount = callbacks.size(); - for (int i = 0; i < callbackCount; i++) { - OnOpModeChangedListener callback = callbacks.valueAt(i); - - if (onlyForeground && (callback.getFlags() - & WATCH_FOREGROUND_CHANGES) == 0) { - continue; - } - - ArraySet changedPackages = callbackSpecs.get(callback); - if (changedPackages == null) { - changedPackages = new ArraySet<>(); - callbackSpecs.put(callback, changedPackages); - } - changedPackages.add(uidPackageName); - } - } - } - - if (callbackSpecs != null && callbackToIgnore != null) { - callbackSpecs.remove(mModeWatchers.get(callbackToIgnore.asBinder())); - } - } - - if (callbackSpecs == null) { - return; - } - - for (int i = 0; i < callbackSpecs.size(); i++) { - final OnOpModeChangedListener callback = callbackSpecs.keyAt(i); - final ArraySet reportedPackageNames = callbackSpecs.valueAt(i); - if (reportedPackageNames == null) { - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyOpChanged, - this, callback, code, uid, (String) null)); - - } else { - final int reportedPackageCount = reportedPackageNames.size(); - for (int j = 0; j < reportedPackageCount; j++) { - final String reportedPackageName = reportedPackageNames.valueAt(j); - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyOpChanged, - this, callback, code, uid, reportedPackageName)); - } - } - } + ModeCallback listenerToIgnore = callbackToIgnore != null + ? mModeWatchers.get(callbackToIgnore.asBinder()) : null; + mAppOpsCheckingService.notifyOpChangedForAllPkgsInUid(code, uid, onlyForeground, + listenerToIgnore); } private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) { @@ -2068,15 +1991,19 @@ public class AppOpsService extends IAppOpsService.Stub { if (op.getMode() != mode) { previousMode = op.getMode(); op.setMode(mode); + + if (uidState != null) { + uidState.evalForegroundOps(); + } ArraySet cbs = - mOpModeWatchers.get(code); + mAppOpsCheckingService.getOpModeChangedListeners(code); if (cbs != null) { if (repCbs == null) { repCbs = new ArraySet<>(); } repCbs.addAll(cbs); } - cbs = mPackageModeWatchers.get(packageName); + cbs = mAppOpsCheckingService.getPackageModeChangedListeners(packageName); if (cbs != null) { if (repCbs == null) { repCbs = new ArraySet<>(); @@ -2115,42 +2042,9 @@ public class AppOpsService extends IAppOpsService.Stub { } } - private void notifyOpChanged(OnOpModeChangedListener onModeChangedListener, int code, + private void notifyOpChanged(OnOpModeChangedListener callback, int code, int uid, String packageName) { - Objects.requireNonNull(onModeChangedListener); - - if (uid != UID_ANY && onModeChangedListener.getWatchingUid() >= 0 - && onModeChangedListener.getWatchingUid() != uid) { - return; - } - - // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE - int[] switchedCodes; - if (onModeChangedListener.getWatchedOpCode() == ALL_OPS) { - switchedCodes = mSwitchedOps.get(code); - } else if (onModeChangedListener.getWatchedOpCode() == OP_NONE) { - switchedCodes = new int[]{code}; - } else { - switchedCodes = new int[]{onModeChangedListener.getWatchedOpCode()}; - } - - for (int switchedCode : 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. - final long identity = Binder.clearCallingIdentity(); - try { - if (shouldIgnoreCallback(switchedCode, onModeChangedListener.getCallingPid(), - onModeChangedListener.getCallingUid())) { - continue; - } - onModeChangedListener.onOpModeChanged(switchedCode, uid, packageName); - } catch (RemoteException e) { - /* ignore */ - } finally { - Binder.restoreCallingIdentity(identity); - } - } + mAppOpsCheckingService.notifyOpChanged(callback, code, uid, packageName); } private static ArrayList addChange(ArrayList reports, @@ -2249,9 +2143,11 @@ public class AppOpsService extends IAppOpsService.Stub { uidState.setUidMode(code, newMode); for (String packageName : getPackagesForUid(uidState.uid)) { callbacks = addCallbacks(callbacks, code, uidState.uid, packageName, - previousMode, mOpModeWatchers.get(code)); + previousMode, + mAppOpsCheckingService.getOpModeChangedListeners(code)); callbacks = addCallbacks(callbacks, code, uidState.uid, packageName, - previousMode, mPackageModeWatchers.get(packageName)); + previousMode, mAppOpsCheckingService + .getPackageModeChangedListeners(packageName)); allChanges = addChange(allChanges, code, uidState.uid, packageName, previousMode); @@ -2300,9 +2196,11 @@ public class AppOpsService extends IAppOpsService.Stub { uidChanged = true; final int uid = curOp.uidState.uid; callbacks = addCallbacks(callbacks, curOp.op, uid, packageName, - previousMode, mOpModeWatchers.get(curOp.op)); + previousMode, + mAppOpsCheckingService.getOpModeChangedListeners(curOp.op)); callbacks = addCallbacks(callbacks, curOp.op, uid, packageName, - previousMode, mPackageModeWatchers.get(packageName)); + previousMode, mAppOpsCheckingService + .getPackageModeChangedListeners(packageName)); allChanges = addChange(allChanges, curOp.op, uid, packageName, previousMode); @@ -2318,6 +2216,9 @@ public class AppOpsService extends IAppOpsService.Stub { UserHandle.getUserId(uidState.uid)); } } + if (uidChanged) { + uidState.evalForegroundOps(); + } } if (changed) { @@ -2394,6 +2295,15 @@ public class AppOpsService extends IAppOpsService.Stub { dpmi.resetOp(op, packageName, userId); } + private void evalAllForegroundOpsLocked() { + for (int uidi = mUidStates.size() - 1; uidi >= 0; uidi--) { + final UidState uidState = mUidStates.valueAt(uidi); + if (uidState.foregroundOps != null) { + uidState.evalForegroundOps(); + } + } + } + @Override public void startWatchingMode(int op, String packageName, IAppOpsCallback callback) { startWatchingModeWithFlags(op, packageName, 0, callback); @@ -2437,21 +2347,12 @@ public class AppOpsService extends IAppOpsService.Stub { mModeWatchers.put(callback.asBinder(), cb); } if (switchOp != AppOpsManager.OP_NONE) { - ArraySet cbs = mOpModeWatchers.get(switchOp); - if (cbs == null) { - cbs = new ArraySet<>(); - mOpModeWatchers.put(switchOp, cbs); - } - cbs.add(cb); + mAppOpsCheckingService.startWatchingOpModeChanged(cb, switchOp); } if (mayWatchPackageName) { - ArraySet cbs = mPackageModeWatchers.get(packageName); - if (cbs == null) { - cbs = new ArraySet<>(); - mPackageModeWatchers.put(packageName, cbs); - } - cbs.add(cb); + mAppOpsCheckingService.startWatchingPackageModeChanged(cb, packageName); } + evalAllForegroundOpsLocked(); } } @@ -2464,21 +2365,10 @@ public class AppOpsService extends IAppOpsService.Stub { ModeCallback cb = mModeWatchers.remove(callback.asBinder()); if (cb != null) { cb.unlinkToDeath(); - for (int i = mOpModeWatchers.size() - 1; i >= 0; i--) { - ArraySet cbs = mOpModeWatchers.valueAt(i); - cbs.remove(cb); - if (cbs.size() <= 0) { - mOpModeWatchers.removeAt(i); - } - } - for (int i = mPackageModeWatchers.size() - 1; i >= 0; i--) { - ArraySet cbs = mPackageModeWatchers.valueAt(i); - cbs.remove(cb); - if (cbs.size() <= 0) { - mPackageModeWatchers.removeAt(i); - } - } + mAppOpsCheckingService.removeListener(cb); } + + evalAllForegroundOpsLocked(); } } @@ -3831,7 +3721,7 @@ public class AppOpsService extends IAppOpsService.Stub { /** * Create a restriction description matching the properties of the package. * - * @param packageState The package to create the restriction description for + * @param pkg The package to create the restriction description for * * @return The restriction matching the package */ @@ -5261,55 +5151,8 @@ public class AppOpsService extends IAppOpsService.Stub { pw.println(); } - if (mOpModeWatchers.size() > 0 && !dumpHistory) { - boolean printedHeader = false; - for (int i = 0; i < mOpModeWatchers.size(); i++) { - if (dumpOp >= 0 && dumpOp != mOpModeWatchers.keyAt(i)) { - continue; - } - boolean printedOpHeader = false; - ArraySet callbacks = mOpModeWatchers.valueAt(i); - for (int j = 0; j < callbacks.size(); j++) { - final OnOpModeChangedListener cb = callbacks.valueAt(j); - if (dumpPackage != null - && dumpUid != UserHandle.getAppId(cb.getWatchingUid())) { - continue; - } - needSep = true; - if (!printedHeader) { - pw.println(" Op mode watchers:"); - printedHeader = true; - } - if (!printedOpHeader) { - pw.print(" Op "); - pw.print(AppOpsManager.opToName(mOpModeWatchers.keyAt(i))); - pw.println(":"); - printedOpHeader = true; - } - pw.print(" #"); pw.print(j); pw.print(": "); - pw.println(cb); - } - } - } - if (mPackageModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) { - boolean printedHeader = false; - for (int i = 0; i < mPackageModeWatchers.size(); i++) { - if (dumpPackage != null && !dumpPackage.equals(mPackageModeWatchers.keyAt(i))) { - continue; - } - needSep = true; - if (!printedHeader) { - pw.println(" Package mode watchers:"); - printedHeader = true; - } - pw.print(" Pkg "); pw.print(mPackageModeWatchers.keyAt(i)); - pw.println(":"); - ArraySet callbacks = mPackageModeWatchers.valueAt(i); - for (int j = 0; j < callbacks.size(); j++) { - pw.print(" #"); pw.print(j); pw.print(": "); - pw.println(callbacks.valueAt(j)); - } - } + if (!dumpHistory) { + needSep |= mAppOpsCheckingService.dumpListeners(dumpOp, dumpUid, dumpPackage, pw); } if (mModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) { @@ -5509,6 +5352,11 @@ public class AppOpsService extends IAppOpsService.Stub { } } } + if (uidState.foregroundOps != null && !hasOp) { + if (uidState.foregroundOps.indexOfKey(dumpOp) > 0) { + hasOp = true; + } + } if (!hasOp || !hasPackage || !hasMode) { continue; } @@ -5516,6 +5364,21 @@ public class AppOpsService extends IAppOpsService.Stub { pw.print(" Uid "); UserHandle.formatUid(pw, uidState.uid); pw.println(":"); uidState.dump(pw, nowElapsed); + if (uidState.foregroundOps != null && (dumpMode < 0 + || dumpMode == AppOpsManager.MODE_FOREGROUND)) { + pw.println(" foregroundOps:"); + for (int j = 0; j < uidState.foregroundOps.size(); j++) { + if (dumpOp >= 0 && dumpOp != uidState.foregroundOps.keyAt(j)) { + continue; + } + pw.print(" "); + pw.print(AppOpsManager.opToName(uidState.foregroundOps.keyAt(j))); + pw.print(": "); + pw.println(uidState.foregroundOps.valueAt(j) ? "WATCHER" : "SILENT"); + } + pw.print(" hasForegroundWatchers="); + pw.println(uidState.hasForegroundWatchers); + } needSep = true; if (opModes != null) { @@ -5717,7 +5580,7 @@ public class AppOpsService extends IAppOpsService.Stub { private void notifyWatchersOfChange(int code, int uid) { final ArraySet modeChangedListenerSet; synchronized (this) { - modeChangedListenerSet = mOpModeWatchers.get(code); + modeChangedListenerSet = mAppOpsCheckingService.getOpModeChangedListeners(code); if (modeChangedListenerSet == null) { return; } diff --git a/services/core/java/com/android/server/appop/AppOpsServiceTestingShim.java b/services/core/java/com/android/server/appop/AppOpsServiceTestingShim.java index c605375e1d3f5..6aaf19049f005 100644 --- a/services/core/java/com/android/server/appop/AppOpsServiceTestingShim.java +++ b/services/core/java/com/android/server/appop/AppOpsServiceTestingShim.java @@ -16,9 +16,11 @@ package com.android.server.appop; +import android.util.ArraySet; import android.util.SparseBooleanArray; import android.util.SparseIntArray; +import java.io.PrintWriter; import java.util.Objects; /** @@ -179,24 +181,109 @@ public class AppOpsServiceTestingShim implements AppOpsCheckingServiceInterface } @Override - public SparseBooleanArray getForegroundOps(int uid) { - SparseBooleanArray oldVal = mOldImplementation.getForegroundOps(uid); - SparseBooleanArray newVal = mNewImplementation.getForegroundOps(uid); + public void startWatchingOpModeChanged(OnOpModeChangedListener changedListener, int op) { + mOldImplementation.startWatchingOpModeChanged(changedListener, op); + mNewImplementation.startWatchingOpModeChanged(changedListener, op); + } + + @Override + public void startWatchingPackageModeChanged(OnOpModeChangedListener changedListener, + String packageName) { + mOldImplementation.startWatchingPackageModeChanged(changedListener, packageName); + mNewImplementation.startWatchingPackageModeChanged(changedListener, packageName); + } + + @Override + public void removeListener(OnOpModeChangedListener changedListener) { + mOldImplementation.removeListener(changedListener); + mNewImplementation.removeListener(changedListener); + } + + @Override + public ArraySet getOpModeChangedListeners(int op) { + ArraySet oldVal = mOldImplementation.getOpModeChangedListeners(op); + ArraySet newVal = mNewImplementation.getOpModeChangedListeners(op); if (!Objects.equals(oldVal, newVal)) { - signalImplDifference("getForegroundOps"); + signalImplDifference("getOpModeChangedListeners"); } return newVal; } @Override - public SparseBooleanArray getForegroundOps(String packageName, int userId) { - SparseBooleanArray oldVal = mOldImplementation.getForegroundOps(packageName, userId); - SparseBooleanArray newVal = mNewImplementation.getForegroundOps(packageName, userId); + public ArraySet getPackageModeChangedListeners(String packageName) { + ArraySet oldVal = mOldImplementation + .getPackageModeChangedListeners(packageName); + ArraySet newVal = mNewImplementation + .getPackageModeChangedListeners(packageName); if (!Objects.equals(oldVal, newVal)) { - signalImplDifference("getForegroundOps"); + signalImplDifference("getPackageModeChangedListeners"); + } + + return newVal; + } + + @Override + public void notifyWatchersOfChange(int op, int uid) { + mOldImplementation.notifyWatchersOfChange(op, uid); + mNewImplementation.notifyWatchersOfChange(op, uid); + } + + @Override + public void notifyOpChanged(OnOpModeChangedListener changedListener, int op, int uid, + String packageName) { + mOldImplementation.notifyOpChanged(changedListener, op, uid, packageName); + mNewImplementation.notifyOpChanged(changedListener, op, uid, packageName); + } + + @Override + public void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground, + OnOpModeChangedListener callbackToIgnore) { + mOldImplementation + .notifyOpChangedForAllPkgsInUid(op, uid, onlyForeground, callbackToIgnore); + mNewImplementation + .notifyOpChangedForAllPkgsInUid(op, uid, onlyForeground, callbackToIgnore); + } + + @Override + public SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps) { + SparseBooleanArray oldVal = mOldImplementation.evalForegroundUidOps(uid, foregroundOps); + SparseBooleanArray newVal = mNewImplementation.evalForegroundUidOps(uid, foregroundOps); + + if (!Objects.equals(oldVal, newVal)) { + signalImplDifference("evalForegroundUidOps"); + } + + return newVal; + } + + @Override + public SparseBooleanArray evalForegroundPackageOps(String packageName, + SparseBooleanArray foregroundOps, int userId) { + SparseBooleanArray oldVal = mOldImplementation + .evalForegroundPackageOps(packageName, foregroundOps, userId); + SparseBooleanArray newVal = mNewImplementation + .evalForegroundPackageOps(packageName, foregroundOps, userId); + + if (!Objects.equals(oldVal, newVal)) { + signalImplDifference("evalForegroundPackageOps"); + } + + return newVal; + } + + @Override + public boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, + PrintWriter printWriter) { + boolean oldVal = mOldImplementation + .dumpListeners(dumpOp, dumpUid, dumpPackage, printWriter); + boolean newVal = mNewImplementation + .dumpListeners(dumpOp, dumpUid, dumpPackage, printWriter); + + if (oldVal != newVal) { + signalImplDifference("dumpListeners"); } 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 17c92acd84a27..4caf6cc257b50 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,21 +16,30 @@ package com.android.server.permission.access.appop +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.annotations.VisibleForTesting 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.IndexedMap -import com.android.server.permission.access.collection.IntBooleanMap -import com.android.server.permission.access.collection.IntMap -import com.android.server.permission.access.collection.forEachIndexed +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 @@ -175,23 +184,308 @@ class AppOpService( // and we have our own persistence. } - override fun getForegroundOps(uid: Int): IntBooleanMap { - return IntBooleanMap().apply { - getUidModes(uid)?.forEachIndexed { _, code, mode -> - if (mode == AppOpsManager.MODE_FOREGROUND) { - put(AppOpsManager.strOpToOp(code), true) - } + // code -> listeners + private val opModeWatchers = IntMap>() + + // packageName -> listeners + private val packageModeWatchers = IndexedMap>() + + override fun startWatchingOpModeChanged(changedListener: OnOpModeChangedListener, op: Int) { + synchronized(lock) { + opModeWatchers.getOrPut(op) { IndexedSet() } += changedListener + } + } + + override fun startWatchingPackageModeChanged( + changedListener: OnOpModeChangedListener, + packageName: String + ) { + synchronized(lock) { + packageModeWatchers.getOrPut(packageName) { IndexedSet() } += changedListener + } + } + + override fun removeListener(changedListener: OnOpModeChangedListener) { + synchronized(lock) { + opModeWatchers.removeAllIndexed { _, _, listeners -> + listeners -= changedListener + listeners.isEmpty() + } + packageModeWatchers.removeAllIndexed { _, _, listeners -> + listeners -= changedListener + listeners.isEmpty() } } } - override fun getForegroundOps(packageName: String, userId: Int): IntBooleanMap { - return IntBooleanMap().apply { - getPackageModes(packageName, userId)?.forEachIndexed { _, code, mode -> - if (mode == AppOpsManager.MODE_FOREGROUND) { - put(AppOpsManager.strOpToOp(code), true) - } + override fun getOpModeChangedListeners(op: Int): IndexedSet { + synchronized(lock) { + val listeners = opModeWatchers[op] + return if (listeners == null) { + IndexedSet() + } else { + IndexedSet(listeners) } } } + + override fun getPackageModeChangedListeners( + packageName: String + ): IndexedSet { + synchronized(lock) { + val listeners = packageModeWatchers[packageName] + return if (listeners == null) { + IndexedSet() + } else { + IndexedSet(listeners) + } + } + } + + override fun notifyWatchersOfChange(op: Int, uid: Int) { + val listeners = getOpModeChangedListeners(op) + listeners.forEachIndexed { _, listener -> + notifyOpChanged(listener, op, uid, null) + } + } + + override fun notifyOpChanged( + changedListener: OnOpModeChangedListener, + op: Int, + uid: Int, + packageName: String? + ) { + 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? + ) { + val uidPackageNames = getPackagesForUid(uid) + val callbackSpecs = IndexedMap>() + + fun associateListenerWithPackageNames( + listener: OnOpModeChangedListener, + packageNames: Array + ) { + 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 { + // 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? { + synchronized(lock) { + val uidModes = getUidModes(uid) + return evalForegroundOps(uidModes, foregroundOps) + } + } + + override fun evalForegroundPackageOps( + packageName: String, + foregroundOps: SparseBooleanArray?, + @UserIdInt userId: Int + ): SparseBooleanArray? { + synchronized(lock) { + val ops = service.getState { getPackageModes(packageName, userId) } + return evalForegroundOps(ops, foregroundOps) + } + } + + private fun evalForegroundOps( + ops: IndexedMap?, + 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( + dumpOp: Int, + dumpUid: Int, + dumpPackage: String?, + printWriter: PrintWriter + ): Boolean { + 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/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java index 197342874b2aa..021d01cca381c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java @@ -57,7 +57,7 @@ public class AppOpsLegacyRestrictionsTest { Handler mHandler; @Mock - AppOpsRestrictions.AppOpsRestrictionRemovedListener mRestrictionRemovedListener; + AppOpsCheckingServiceInterface mLegacyAppOpsService; AppOpsRestrictions mAppOpsRestrictions; @@ -75,8 +75,7 @@ public class AppOpsLegacyRestrictionsTest { r.run(); return true; }); - mAppOpsRestrictions = new AppOpsRestrictionsImpl(mContext, mHandler, - mRestrictionRemovedListener); + mAppOpsRestrictions = new AppOpsRestrictionsImpl(mContext, mHandler, mLegacyAppOpsService); } @After @@ -272,7 +271,7 @@ public class AppOpsLegacyRestrictionsTest { public void testNotify() { mAppOpsRestrictions.setUserRestriction(mClientToken, mUserId1, mOpCode1, true, null); mAppOpsRestrictions.clearUserRestrictions(mClientToken); - Mockito.verify(mRestrictionRemovedListener, Mockito.times(1)) - .onAppOpsRestrictionRemoved(mOpCode1); + Mockito.verify(mLegacyAppOpsService, Mockito.times(1)) + .notifyWatchersOfChange(mOpCode1, UID_ANY); } }