From 90e99803fe7d807176a44f141a4560766e218a33 Mon Sep 17 00:00:00 2001 From: Pyuli Naithani Date: Mon, 22 Aug 2022 15:51:45 +0000 Subject: [PATCH] Revert "Revert "AppOps refactor for mode watchers."" This reverts commit 31c2991950682e5e446d7200a54dab2682e34f01. Reason for revert: Fixing the bug that caused the initial revert The cause for the revert was that we called notifyOpChanged asyncly but in the refactor we separated the listener callback and listener details in OpModeChangedListener and ModeChangedListenerDetails respectively. But we only passed OpModeChangedListener to notifyOpChanged relying on an internal map to get the corresponding ModeChangedListenerDetails but this relied on the call being synchronous. With this CL we pass an object of ModeChangedListenerDetails within OpModeChangedListener. BUG: 240200048 Test: Manual Change-Id: I5484eddb0a4081145dfdc8a638950b437ca15e8f --- .../android/server/appop/AppOpsService.java | 394 +++++------------- .../server/appop/AppOpsServiceInterface.java | 99 ++++- .../LegacyAppOpsServiceInterfaceImpl.java | 379 ++++++++++++++++- .../server/appop/OnOpModeChangedListener.java | 102 +++++ 4 files changed, 678 insertions(+), 296 deletions(-) create mode 100644 services/core/java/com/android/server/appop/OnOpModeChangedListener.java diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index a5bcb0517d255..248e35e6964d2 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -63,7 +63,6 @@ import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE; import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED; import static android.app.AppOpsManager.UID_STATE_PERSISTENT; import static android.app.AppOpsManager.UID_STATE_TOP; -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; @@ -563,6 +562,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch public ArrayMap pkgOps; // 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; @@ -658,48 +658,24 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch return mode; } - private void evalForegroundWatchers(int op, SparseArray> watchers, - SparseBooleanArray which) { - boolean curValue = which.get(op, false); - ArraySet callbacks = watchers.get(op); - if (callbacks != null) { - for (int cbi = callbacks.size() - 1; !curValue && cbi >= 0; cbi--) { - if ((callbacks.valueAt(cbi).mFlags - & AppOpsManager.WATCH_FOREGROUND_CHANGES) != 0) { - hasForegroundWatchers = true; - curValue = true; - } - } - } - which.put(op, curValue); - } - - public void evalForegroundOps(SparseArray> watchers) { - SparseBooleanArray which = null; - hasForegroundWatchers = false; - final SparseIntArray opModes = getNonDefaultUidModes(); - for (int i = opModes.size() - 1; i >= 0; i--) { - if (opModes.valueAt(i) == AppOpsManager.MODE_FOREGROUND) { - if (which == null) { - which = new SparseBooleanArray(); - } - evalForegroundWatchers(opModes.keyAt(i), watchers, which); - } - } + public void evalForegroundOps() { + foregroundOps = null; + foregroundOps = mAppOpsServiceInterface.evalForegroundUidOps(uid, foregroundOps); if (pkgOps != null) { for (int i = pkgOps.size() - 1; i >= 0; i--) { - Ops ops = pkgOps.valueAt(i); - for (int j = ops.size() - 1; j >= 0; j--) { - if (ops.valueAt(j).getMode() == AppOpsManager.MODE_FOREGROUND) { - if (which == null) { - which = new SparseBooleanArray(); - } - evalForegroundWatchers(ops.keyAt(j), watchers, which); - } + foregroundOps = mAppOpsServiceInterface + .evalForegroundPackageOps(pkgOps.valueAt(i).packageName, foregroundOps); + } + } + hasForegroundWatchers = false; + if (foregroundOps != null) { + for (int i = 0; i < foregroundOps.size(); i++) { + if (foregroundOps.valueAt(i)) { + hasForegroundWatchers = true; + break; } } } - foregroundOps = which; } } @@ -1562,33 +1538,24 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } - final SparseArray> mOpModeWatchers = new SparseArray<>(); - final ArrayMap> mPackageModeWatchers = new ArrayMap<>(); final ArrayMap mModeWatchers = new ArrayMap<>(); final ArrayMap> mActiveWatchers = new ArrayMap<>(); final ArrayMap> mStartedWatchers = new ArrayMap<>(); final ArrayMap> mNotedWatchers = new ArrayMap<>(); final AudioRestrictionManager mAudioRestrictionManager = new AudioRestrictionManager(); - final class ModeCallback implements DeathRecipient { + final class ModeCallback extends OnOpModeChangedListener implements DeathRecipient { /** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */ public static final int ALL_OPS = -2; - final IAppOpsCallback mCallback; - final int mWatchingUid; - final int mFlags; - final int mWatchedOpCode; - final int mCallingUid; - final int mCallingPid; + // Need to keep this only because stopWatchingMode needs an IAppOpsCallback. + // Otherwise we can just use the IBinder object. + private final IAppOpsCallback mCallback; - ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOp, + ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOpCode, int callingUid, int callingPid) { - mCallback = callback; - mWatchingUid = watchingUid; - mFlags = flags; - mWatchedOpCode = watchedOp; - mCallingUid = callingUid; - mCallingPid = callingPid; + super(watchingUid, flags, watchedOpCode, callingUid, callingPid); + this.mCallback = callback; try { mCallback.asBinder().linkToDeath(this, 0); } catch (RemoteException e) { @@ -1596,20 +1563,16 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } - public boolean isWatchingUid(int uid) { - return uid == UID_ANY || mWatchingUid < 0 || mWatchingUid == uid; - } - @Override public String toString() { StringBuilder sb = new StringBuilder(128); sb.append("ModeCallback{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(" watchinguid="); - UserHandle.formatUid(sb, mWatchingUid); + UserHandle.formatUid(sb, getWatchingUid()); sb.append(" flags=0x"); - sb.append(Integer.toHexString(mFlags)); - switch (mWatchedOpCode) { + sb.append(Integer.toHexString(getFlags())); + switch (getWatchedOpCode()) { case OP_NONE: break; case ALL_OPS: @@ -1617,13 +1580,13 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch break; default: sb.append(" op="); - sb.append(opToName(mWatchedOpCode)); + sb.append(opToName(getWatchedOpCode())); break; } sb.append(" from uid="); - UserHandle.formatUid(sb, mCallingUid); + UserHandle.formatUid(sb, getCallingUid()); sb.append(" pid="); - sb.append(mCallingPid); + sb.append(getCallingPid()); sb.append('}'); return sb.toString(); } @@ -1636,6 +1599,11 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch public void binderDied() { stopWatchingMode(mCallback); } + + @Override + public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException { + mCallback.opChanged(op, uid, packageName); + } } final class ActiveCallback implements DeathRecipient { @@ -1804,7 +1772,14 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch public AppOpsService(File storagePath, Handler handler, Context context) { mContext = context; - mAppOpsServiceInterface = new LegacyAppOpsServiceInterfaceImpl(this, this); + + for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) { + int switchCode = AppOpsManager.opToSwitch(switchedCode); + mSwitchedOps.put(switchCode, + ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode)); + } + mAppOpsServiceInterface = + new LegacyAppOpsServiceInterfaceImpl(this, this, handler, context, mSwitchedOps); LockGuard.installLock(this, LockGuard.INDEX_APP_OPS); mFile = new AtomicFile(storagePath, "appops"); @@ -1818,12 +1793,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch mHandler = handler; mConstants = new Constants(mHandler); readState(); - - for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) { - int switchCode = AppOpsManager.opToSwitch(switchedCode); - mSwitchedOps.put(switchCode, - ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode)); - } } public void publish() { @@ -1982,20 +1951,20 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch final String[] changedPkgs = intent.getStringArrayExtra( Intent.EXTRA_CHANGED_PACKAGE_LIST); for (int code : OPS_RESTRICTED_ON_SUSPEND) { - ArraySet callbacks; + ArraySet onModeChangedListeners; synchronized (AppOpsService.this) { - callbacks = mOpModeWatchers.get(code); - if (callbacks == null) { + onModeChangedListeners = + mAppOpsServiceInterface.getOpModeChangedListeners(code); + if (onModeChangedListeners == null) { continue; } - callbacks = new ArraySet<>(callbacks); } for (int i = 0; i < changedUids.length; i++) { final int changedUid = changedUids[i]; final String changedPkg = changedPkgs[i]; // We trust packagemanager to insert matching uid and packageNames in the // extras - notifyOpChanged(callbacks, code, changedUid, changedPkg); + notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg); } } } @@ -2596,7 +2565,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch if (!uidState.setUidMode(code, mode)) { return; } - uidState.evalForegroundOps(mOpModeWatchers); + uidState.evalForegroundOps(); if (mode != MODE_ERRORED && mode != previousMode) { updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid); } @@ -2615,78 +2584,10 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch */ 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++) { - ModeCallback callback = callbacks.valueAt(i); - if (onlyForeground && (callback.mFlags & 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++) { - ModeCallback callback = callbacks.valueAt(i); - if (onlyForeground && (callback.mFlags & 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 ModeCallback 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; + mAppOpsServiceInterface.notifyOpChangedForAllPkgsInUid(code, uid, onlyForeground, + listenerToIgnore); } private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) { @@ -2810,7 +2711,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch return; } - ArraySet repCbs = null; + ArraySet repCbs = null; code = AppOpsManager.opToSwitch(code); PackageVerificationResult pvr; @@ -2831,16 +2732,17 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch op.setMode(mode); if (uidState != null) { - uidState.evalForegroundOps(mOpModeWatchers); + uidState.evalForegroundOps(); } - ArraySet cbs = mOpModeWatchers.get(code); + ArraySet cbs = + mAppOpsServiceInterface.getOpModeChangedListeners(code); if (cbs != null) { if (repCbs == null) { repCbs = new ArraySet<>(); } repCbs.addAll(cbs); } - cbs = mPackageModeWatchers.get(packageName); + cbs = mAppOpsServiceInterface.getPackageModeChangedListeners(packageName); if (cbs != null) { if (repCbs == null) { repCbs = new ArraySet<>(); @@ -2871,47 +2773,17 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch notifyOpChangedSync(code, uid, packageName, mode, previousMode); } - private void notifyOpChanged(ArraySet callbacks, int code, + private void notifyOpChanged(ArraySet callbacks, int code, int uid, String packageName) { for (int i = 0; i < callbacks.size(); i++) { - final ModeCallback callback = callbacks.valueAt(i); + final OnOpModeChangedListener callback = callbacks.valueAt(i); notifyOpChanged(callback, code, uid, packageName); } } - private void notifyOpChanged(ModeCallback callback, int code, + private void notifyOpChanged(OnOpModeChangedListener callback, int code, int uid, String packageName) { - if (uid != UID_ANY && callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) { - return; - } - - // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE - int[] switchedCodes; - if (callback.mWatchedOpCode == ALL_OPS) { - switchedCodes = mSwitchedOps.get(code); - } else if (callback.mWatchedOpCode == OP_NONE) { - switchedCodes = new int[]{code}; - } else { - switchedCodes = new int[]{callback.mWatchedOpCode}; - } - - 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, callback.mCallingPid, - callback.mCallingUid)) { - continue; - } - callback.mCallback.opChanged(switchedCode, uid, packageName); - } catch (RemoteException e) { - /* ignore */ - } finally { - Binder.restoreCallingIdentity(identity); - } - } + mAppOpsServiceInterface.notifyOpChanged(callback, code, uid, packageName); } private static ArrayList addChange(ArrayList reports, @@ -2936,9 +2808,10 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch return reports; } - private static HashMap> addCallbacks( - HashMap> callbacks, - int op, int uid, String packageName, int previousMode, ArraySet cbs) { + private static HashMap> addCallbacks( + HashMap> callbacks, + int op, int uid, String packageName, int previousMode, + ArraySet cbs) { if (cbs == null) { return callbacks; } @@ -2947,7 +2820,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } final int N = cbs.size(); for (int i=0; i reports = callbacks.get(cb); ArrayList changed = addChange(reports, op, uid, packageName, previousMode); if (changed != reports) { @@ -2990,7 +2863,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch enforceManageAppOpsModes(callingPid, callingUid, reqUid); - HashMap> callbacks = null; + HashMap> callbacks = null; ArrayList allChanges = new ArrayList<>(); synchronized (this) { boolean changed = false; @@ -3007,9 +2880,11 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code)); for (String packageName : getPackagesForUid(uidState.uid)) { callbacks = addCallbacks(callbacks, code, uidState.uid, packageName, - previousMode, mOpModeWatchers.get(code)); + previousMode, + mAppOpsServiceInterface.getOpModeChangedListeners(code)); callbacks = addCallbacks(callbacks, code, uidState.uid, packageName, - previousMode, mPackageModeWatchers.get(packageName)); + previousMode, mAppOpsServiceInterface + .getPackageModeChangedListeners(packageName)); allChanges = addChange(allChanges, code, uidState.uid, packageName, previousMode); @@ -3053,9 +2928,11 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch uidChanged = true; final int uid = curOp.uidState.uid; callbacks = addCallbacks(callbacks, curOp.op, uid, packageName, - previousMode, mOpModeWatchers.get(curOp.op)); + previousMode, + mAppOpsServiceInterface.getOpModeChangedListeners(curOp.op)); callbacks = addCallbacks(callbacks, curOp.op, uid, packageName, - previousMode, mPackageModeWatchers.get(packageName)); + previousMode, mAppOpsServiceInterface + .getPackageModeChangedListeners(packageName)); allChanges = addChange(allChanges, curOp.op, uid, packageName, previousMode); @@ -3075,7 +2952,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch mUidStates.remove(uidState.uid); } if (uidChanged) { - uidState.evalForegroundOps(mOpModeWatchers); + uidState.evalForegroundOps(); } } @@ -3084,8 +2961,9 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } if (callbacks != null) { - for (Map.Entry> ent : callbacks.entrySet()) { - ModeCallback cb = ent.getKey(); + for (Map.Entry> ent + : callbacks.entrySet()) { + OnOpModeChangedListener cb = ent.getKey(); ArrayList reports = ent.getValue(); for (int i=0; i= 0; uidi--) { final UidState uidState = mUidStates.valueAt(uidi); if (uidState.foregroundOps != null) { - uidState.evalForegroundOps(mOpModeWatchers); + uidState.evalForegroundOps(); } } } @@ -3169,20 +3047,10 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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); + mAppOpsServiceInterface.startWatchingOpModeChanged(cb, switchOp); } if (mayWatchPackageName) { - ArraySet cbs = mPackageModeWatchers.get(packageName); - if (cbs == null) { - cbs = new ArraySet<>(); - mPackageModeWatchers.put(packageName, cbs); - } - cbs.add(cb); + mAppOpsServiceInterface.startWatchingPackageModeChanged(cb, packageName); } evalAllForegroundOpsLocked(); } @@ -3197,21 +3065,9 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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); - } - } + mAppOpsServiceInterface.removeListener(cb); } + evalAllForegroundOpsLocked(); } } @@ -4542,12 +4398,14 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch AppOpsService::notifyOpChangedForAllPkgsInUid, this, code, uidState.uid, true, null)); } else if (uidState.pkgOps != null) { - final ArraySet callbacks = mOpModeWatchers.get(code); - if (callbacks != null) { - for (int cbi = callbacks.size() - 1; cbi >= 0; cbi--) { - final ModeCallback callback = callbacks.valueAt(cbi); - if ((callback.mFlags & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0 - || !callback.isWatchingUid(uidState.uid)) { + final ArraySet listenerSet = + mAppOpsServiceInterface.getOpModeChangedListeners(code); + if (listenerSet != null) { + for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) { + final OnOpModeChangedListener listener = listenerSet.valueAt(cbi); + if ((listener.getFlags() + & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0 + || !listener.isWatchingUid(uidState.uid)) { continue; } for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) { @@ -4558,7 +4416,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch if (op.getMode() == AppOpsManager.MODE_FOREGROUND) { mHandler.sendMessage(PooledLambda.obtainMessage( AppOpsService::notifyOpChanged, - this, callback, code, uidState.uid, + this, listenerSet.valueAt(cbi), code, uidState.uid, uidState.pkgOps.keyAt(pkgi))); } } @@ -5045,7 +4903,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } if (changed) { - uidState.evalForegroundOps(mOpModeWatchers); + uidState.evalForegroundOps(); } } } @@ -5131,7 +4989,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch XmlUtils.skipCurrentTag(parser); } } - uidState.evalForegroundOps(mOpModeWatchers); + uidState.evalForegroundOps(); } private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent, @@ -6122,62 +5980,17 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } pw.println(); } - if (mOpModeWatchers.size() > 0 && !dumpHistory) { - boolean printedHeader = false; - for (int i=0; i= 0 && dumpOp != mOpModeWatchers.keyAt(i)) { - continue; - } - boolean printedOpHeader = false; - ArraySet callbacks = mOpModeWatchers.valueAt(i); - for (int j=0; j 0 && dumpOp < 0 && !dumpHistory) { - boolean printedHeader = false; - for (int i=0; i callbacks = mPackageModeWatchers.valueAt(i); - for (int j=0; j 0 && dumpOp < 0 && !dumpHistory) { boolean printedHeader = false; - for (int i=0; i clonedCallbacks; + final ArraySet modeChangedListenerSet; synchronized (this) { - ArraySet callbacks = mOpModeWatchers.get(code); - if (callbacks == null) { + modeChangedListenerSet = mAppOpsServiceInterface.getOpModeChangedListeners(code); + if (modeChangedListenerSet == null) { return; } - clonedCallbacks = new ArraySet<>(callbacks); } - notifyOpChanged(clonedCallbacks, code, uid, null); + notifyOpChanged(modeChangedListenerSet, code, uid, null); } @Override diff --git a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java index cd5ea120f8784..c707086fb2fbe 100644 --- a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java +++ b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java @@ -14,12 +14,20 @@ * limitations under the License. */ package com.android.server.appop; + import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.AppOpsManager.Mode; +import android.util.ArraySet; +import android.util.SparseBooleanArray; import android.util.SparseIntArray; + +import java.io.PrintWriter; + /** * Interface for accessing and modifying modes for app-ops i.e. package and uid modes. - * In the future this interface will also include mode callbacks and op restrictions. + * This interface also includes functions for added and removing op mode watchers. + * In the future this interface will also include op restrictions. */ public interface AppOpsServiceInterface { /** @@ -95,4 +103,93 @@ public interface AppOpsServiceInterface { * Stop tracking app-op modes for all uid and packages. */ void clearAllModes(); + + /** + * 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. + */ + void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, int op); + + /** + * 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. + */ + 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 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. + * @return foregroundOps. + */ + SparseBooleanArray evalForegroundPackageOps(String packageName, + SparseBooleanArray foregroundOps); + + /** + * 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/LegacyAppOpsServiceInterfaceImpl.java b/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java index c27c0d3de5d75..2d498ea2117cb 100644 --- a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java +++ b/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java @@ -16,15 +16,39 @@ package com.android.server.appop; +import static android.app.AppOpsManager.OP_NONE; +import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES; +import static android.app.AppOpsManager.opRestrictsRead; + +import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS; + +import android.Manifest; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.AppOpsManager.Mode; +import android.content.Context; +import android.content.pm.PackageManager; +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.SparseArray; +import android.util.SparseBooleanArray; import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.function.pooled.PooledLambda; + +import libcore.util.EmptyArray; + +import java.io.PrintWriter; +import java.util.Collections; +import java.util.Objects; /** @@ -33,8 +57,13 @@ import com.android.internal.annotations.VisibleForTesting; */ public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface { - // Should be the same object that the AppOpsService is using for locking. + static final String TAG = "LegacyAppOpsServiceInterfaceImpl"; + + // Must be the same object that the AppOpsService is using for locking. final Object mLock; + final Handler mHandler; + final Context mContext; + final SparseArray mSwitchedOps; @GuardedBy("mLock") @VisibleForTesting @@ -43,13 +72,25 @@ public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface @GuardedBy("mLock") final ArrayMap mPackageModes = new ArrayMap<>(); + final SparseArray> mOpModeWatchers = new SparseArray<>(); + final ArrayMap> mPackageModeWatchers = + new ArrayMap<>(); + final PersistenceScheduler mPersistenceScheduler; + // Constant meaning that any UID should be matched when dispatching callbacks + private static final int UID_ANY = -2; + + LegacyAppOpsServiceInterfaceImpl(PersistenceScheduler persistenceScheduler, - @NonNull Object lock) { + @NonNull Object lock, Handler handler, Context context, + SparseArray switchedOps) { this.mPersistenceScheduler = persistenceScheduler; this.mLock = lock; + this.mHandler = handler; + this.mContext = context; + this.mSwitchedOps = switchedOps; } @Override @@ -158,7 +199,6 @@ public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface } } - @Override public boolean areUidModesDefault(int uid) { synchronized (mLock) { @@ -195,4 +235,335 @@ public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface } } -} + @Override + public void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, + int op) { + Objects.requireNonNull(changedListener); + synchronized (mLock) { + ArraySet modeWatcherSet = mOpModeWatchers.get(op); + if (modeWatcherSet == null) { + modeWatcherSet = new ArraySet<>(); + mOpModeWatchers.put(op, modeWatcherSet); + } + modeWatcherSet.add(changedListener); + } + } + + @Override + public void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener, + @NonNull String packageName) { + Objects.requireNonNull(changedListener); + Objects.requireNonNull(packageName); + synchronized (mLock) { + ArraySet modeWatcherSet = + mPackageModeWatchers.get(packageName); + if (modeWatcherSet == null) { + modeWatcherSet = new ArraySet<>(); + mPackageModeWatchers.put(packageName, modeWatcherSet); + } + 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 = 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 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( + LegacyAppOpsServiceInterfaceImpl::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( + LegacyAppOpsServiceInterfaceImpl::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) { + synchronized (mLock) { + return evalForegroundOps(mPackageModes.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()); + } + } + } + + 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; + } + +} \ No newline at end of file diff --git a/services/core/java/com/android/server/appop/OnOpModeChangedListener.java b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java new file mode 100644 index 0000000000000..5ebe8119f0468 --- /dev/null +++ b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appop; + +import android.os.RemoteException; + +/** + * Listener for mode changes, encapsulates methods that should be triggered in the event of a mode + * change. + */ +abstract class OnOpModeChangedListener { + + // Constant meaning that any UID should be matched when dispatching callbacks + private static final int UID_ANY = -2; + + private int mWatchingUid; + private int mFlags; + private int mWatchedOpCode; + private int mCallingUid; + private int mCallingPid; + + OnOpModeChangedListener(int watchingUid, int flags, int watchedOpCode, int callingUid, + int callingPid) { + this.mWatchingUid = watchingUid; + this.mFlags = flags; + this.mWatchedOpCode = watchedOpCode; + this.mCallingUid = callingUid; + this.mCallingPid = callingPid; + } + + /** + * Returns the user id that is watching for the mode change. + */ + public int getWatchingUid() { + return mWatchingUid; + } + + /** + * Returns the flags associated with the mode change listener. + */ + public int getFlags() { + return mFlags; + } + + /** + * Get the app-op whose mode change should trigger the callback. + */ + public int getWatchedOpCode() { + return mWatchedOpCode; + } + + /** + * Get the user-id that triggered the app-op mode change to be watched. + */ + public int getCallingUid() { + return mCallingUid; + } + + /** + * Get the process-id that triggered the app-op mode change to be watched. + */ + public int getCallingPid() { + return mCallingPid; + } + + /** + * returns true if the user id passed in the param is the one that is watching for op mode + * changed. + */ + public boolean isWatchingUid(int uid) { + return uid == UID_ANY || mWatchingUid < 0 || mWatchingUid == uid; + } + + /** + * Method that should be triggered when the app-op's mode is changed. + * @param op app-op whose mode-change is being listened to. + * @param uid user-is associated with the app-op. + * @param packageName package name associated with the app-op. + */ + public abstract void onOpModeChanged(int op, int uid, String packageName) + throws RemoteException; + + /** + * Return human readable string representing the listener. + */ + public abstract String toString(); + +}