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

Commit b2bc651f authored by Philip P. Moltmann's avatar Philip P. Moltmann
Browse files

Callback startWatchingMode with the right op

... in case the op has a switchOp.

Some ops have a switch-op. This means the mode of this op is not unique
to this op but shared with the switch-op.

E.g. FINE_LOCATIONs switch-op is COARSE_LOCATION.

Before R when you registered a startWatchingMode for FINE_LOCATION the
callbacks actually came for COARSE_LOCATION. Now the callbacks will
actually come for FINE_LOCATION

This is a subtle API change, hence guarded by a target-api change-id.

Bug: 148180766
Test: atest CtsAppOpsTestCases:android.app.appops.cts.ForegroundModeTest
Change-Id: I6bcb6f4876e524da65dd54c1d9f5cf1ce14c396c
parent 463979ef
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@ import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.app.usage.UsageStatsManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.Context;
@@ -100,6 +102,18 @@ import java.util.function.Supplier;
 */
@SystemService(Context.APP_OPS_SERVICE)
public class AppOpsManager {
    /**
     * This is a subtle behavior change to {@link #startWatchingMode}.
     *
     * Before this change the system called back for the switched op. After the change the system
     * will call back for the actually requested op or all switched ops if no op is specified.
     *
     * @hide
     */
    @ChangeId
    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
    public static final long CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE = 148180766L;

    /**
     * <p>App ops allows callers to:</p>
     *
+71 −44
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.server.appop;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
import static android.app.AppOpsManager.CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE;
import static android.app.AppOpsManager.FILTER_BY_FEATURE_ID;
import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
@@ -54,6 +55,8 @@ import static android.content.Intent.EXTRA_REPLACING;
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.os.Process.STATSD_UID;

import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;

import static java.lang.Long.max;

import android.Manifest;
@@ -73,6 +76,7 @@ import android.app.AppOpsManager.OpFlags;
import android.app.AppOpsManagerInternal;
import android.app.AppOpsManagerInternal.CheckOpsDelegate;
import android.app.AsyncNotedAppOp;
import android.compat.Compatibility;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -132,7 +136,6 @@ import com.android.internal.app.IAppOpsNotedCallback;
import com.android.internal.app.IAppOpsService;
import com.android.internal.os.Zygote;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.Preconditions;
@@ -294,8 +297,11 @@ public class AppOpsService extends IAppOpsService.Stub {
    @GuardedBy("this")
    private CheckOpsDelegate mCheckOpsDelegate;

    @GuardedBy("this")
    private SparseArray<List<Integer>> mSwitchOpToOps;
    /**
      * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never
      * changed
      */
    private final SparseArray<int[]> mSwitchedOps = new SparseArray<>();

    private ActivityManagerInternal mActivityManagerInternal;

@@ -1194,17 +1200,22 @@ public class AppOpsService extends IAppOpsService.Stub {
    final AudioRestrictionManager mAudioRestrictionManager = new AudioRestrictionManager();

    final class ModeCallback 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;

        ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int callingUid,
                int callingPid) {
        ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOp,
                int callingUid, int callingPid) {
            mCallback = callback;
            mWatchingUid = watchingUid;
            mFlags = flags;
            mWatchedOpCode = watchedOp;
            mCallingUid = callingUid;
            mCallingPid = callingPid;
            try {
@@ -1227,6 +1238,10 @@ public class AppOpsService extends IAppOpsService.Stub {
            UserHandle.formatUid(sb, mWatchingUid);
            sb.append(" flags=0x");
            sb.append(Integer.toHexString(mFlags));
            if (mWatchedOpCode != OP_NONE) {
                sb.append(" op=");
                sb.append(opToName(mWatchedOpCode));
            }
            sb.append(" from uid=");
            UserHandle.formatUid(sb, mCallingUid);
            sb.append(" pid=");
@@ -1346,6 +1361,12 @@ public class AppOpsService extends IAppOpsService.Stub {
        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(Context context) {
@@ -2130,11 +2151,8 @@ public class AppOpsService extends IAppOpsService.Stub {
        }
        String packageName = packageNames[0];

        List<Integer> ops = getSwitchOpToOps().get(switchCode);
        int opsSize = CollectionUtils.size(ops);
        for (int i = 0; i < opsSize; i++) {
            int code = ops.get(i);

        int[] ops = mSwitchedOps.get(switchCode);
        for (int code : ops) {
            String permissionName = AppOpsManager.opToPermission(code);
            if (permissionName == null) {
                continue;
@@ -2212,25 +2230,6 @@ public class AppOpsService extends IAppOpsService.Stub {
        }
    }

    @NonNull
    private SparseArray<List<Integer>> getSwitchOpToOps() {
        synchronized (this) {
            if (mSwitchOpToOps == null) {
                mSwitchOpToOps = new SparseArray<>();
                for (int op = 0; op < _NUM_OP; op++) {
                    int switchOp = AppOpsManager.opToSwitch(op);
                    List<Integer> ops = mSwitchOpToOps.get(switchOp);
                    if (ops == null) {
                        ops = new ArrayList<>();
                        mSwitchOpToOps.put(switchOp, ops);
                    }
                    ops.add(op);
                }
            }
            return mSwitchOpToOps;
        }
    }

    private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode) {
        final StorageManagerInternal storageManagerInternal =
                LocalServices.getService(StorageManagerInternal.class);
@@ -2331,18 +2330,31 @@ public class AppOpsService extends IAppOpsService.Stub {
        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 {
            callback.mCallback.opChanged(code, uid, packageName);
                callback.mCallback.opChanged(switchedCode, uid, packageName);
            } catch (RemoteException e) {
                /* ignore */
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
        }
    }

    private static HashMap<ModeCallback, ArrayList<ChangeRec>> addCallbacks(
            HashMap<ModeCallback, ArrayList<ChangeRec>> callbacks,
@@ -2535,17 +2547,32 @@ public class AppOpsService extends IAppOpsService.Stub {
            return;
        }
        synchronized (this) {
            op = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
            int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;

            // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE
            int notifiedOps;
            if (Compatibility.isChangeEnabled(
                    CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE)) {
                if (op == OP_NONE) {
                    notifiedOps = ALL_OPS;
                } else {
                    notifiedOps = op;
                }
            } else {
                notifiedOps = switchOp;
            }

            ModeCallback cb = mModeWatchers.get(callback.asBinder());
            if (cb == null) {
                cb = new ModeCallback(callback, watchedUid, flags, callingUid, callingPid);
                cb = new ModeCallback(callback, watchedUid, flags, notifiedOps, callingUid,
                        callingPid);
                mModeWatchers.put(callback.asBinder(), cb);
            }
            if (op != AppOpsManager.OP_NONE) {
                ArraySet<ModeCallback> cbs = mOpModeWatchers.get(op);
            if (switchOp != AppOpsManager.OP_NONE) {
                ArraySet<ModeCallback> cbs = mOpModeWatchers.get(switchOp);
                if (cbs == null) {
                    cbs = new ArraySet<>();
                    mOpModeWatchers.put(op, cbs);
                    mOpModeWatchers.put(switchOp, cbs);
                }
                cbs.add(cb);
            }