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

Commit 4374ef85 authored by Philip P. Moltmann's avatar Philip P. Moltmann Committed by Android (Google) Code Review
Browse files

Merge "Add private APIs to watch noted app ops - framework."

parents 7a17e757 b3d2ae26
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -388,6 +388,7 @@ java_defaults {
        "core/java/android/speech/tts/ITextToSpeechService.aidl",
        "core/java/com/android/internal/app/IAppOpsActiveCallback.aidl",
        "core/java/com/android/internal/app/IAppOpsCallback.aidl",
        "core/java/com/android/internal/app/IAppOpsNotedCallback.aidl",
        "core/java/com/android/internal/app/IAppOpsService.aidl",
        "core/java/com/android/internal/app/IBatteryStats.aidl",
        "core/java/com/android/internal/app/ISoundTriggerService.aidl",
+101 −4
Original line number Diff line number Diff line
@@ -41,8 +41,10 @@ import android.os.UserManager;
import android.provider.Settings;
import android.util.ArrayMap;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsActiveCallback;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsNotedCallback;
import com.android.internal.app.IAppOpsService;
import com.android.internal.util.Preconditions;

@@ -86,10 +88,20 @@ public class AppOpsManager {
     */

    final Context mContext;

    @UnsupportedAppUsage
    final IAppOpsService mService;
    final ArrayMap<OnOpChangedListener, IAppOpsCallback> mModeWatchers = new ArrayMap<>();
    final ArrayMap<OnOpActiveChangedListener, IAppOpsActiveCallback> mActiveWatchers =

    @GuardedBy("mModeWatchers")
    private final ArrayMap<OnOpChangedListener, IAppOpsCallback> mModeWatchers =
            new ArrayMap<>();

    @GuardedBy("mActiveWatchers")
    private final ArrayMap<OnOpActiveChangedListener, IAppOpsActiveCallback> mActiveWatchers =
            new ArrayMap<>();

    @GuardedBy("mNotedWatchers")
    private final ArrayMap<OnOpNotedListener, IAppOpsNotedCallback> mNotedWatchers =
            new ArrayMap<>();

    static IBinder sToken;
@@ -2470,6 +2482,23 @@ public class AppOpsManager {
        void onOpActiveChanged(int code, int uid, String packageName, boolean active);
    }

    /**
     * Callback for notification of an op being noted.
     *
     * @hide
     */
    public interface OnOpNotedListener {
        /**
         * Called when an op was noted.
         *
         * @param code The op code.
         * @param uid The UID performing the operation.
         * @param packageName The package performing the operation.
         * @param result The result of the note.
         */
        void onOpNoted(String code, int uid, String packageName, int result);
    }

    /**
     * Callback for notification of changes to operation state.
     * This allows you to see the raw op codes instead of strings.
@@ -2819,7 +2848,7 @@ public class AppOpsManager {
     */
    public void stopWatchingMode(OnOpChangedListener callback) {
        synchronized (mModeWatchers) {
            IAppOpsCallback cb = mModeWatchers.get(callback);
            IAppOpsCallback cb = mModeWatchers.remove(callback);
            if (cb != null) {
                try {
                    mService.stopWatchingMode(cb);
@@ -2893,7 +2922,7 @@ public class AppOpsManager {
    @TestApi
    public void stopWatchingActive(@NonNull OnOpActiveChangedListener callback) {
        synchronized (mActiveWatchers) {
            final IAppOpsActiveCallback cb = mActiveWatchers.get(callback);
            final IAppOpsActiveCallback cb = mActiveWatchers.remove(callback);
            if (cb != null) {
                try {
                    mService.stopWatchingActive(cb);
@@ -2904,6 +2933,74 @@ public class AppOpsManager {
        }
    }

    /**
     * Start watching for noted app ops. An app op may be immediate or long running.
     * Immediate ops are noted while long running ones are started and stopped. This
     * method allows registering a listener to be notified when an app op is noted. If
     * an op is being noted by any package you will get a callback. To change the
     * watched ops for a registered callback you need to unregister and register it again.
     *
     * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission
     * you can watch changes only for your UID.
     *
     * @param ops The ops to watch.
     * @param callback Where to report changes.
     *
     * @see #startWatchingActive(int[], OnOpActiveChangedListener)
     * @see #stopWatchingNoted(OnOpNotedListener)
     * @see #noteOp(String, int, String)
     *
     * @hide
     */
    @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true)
    public void startWatchingNoted(@NonNull String[] ops, @NonNull OnOpNotedListener callback) {
        IAppOpsNotedCallback cb;
        synchronized (mNotedWatchers) {
            cb = mNotedWatchers.get(callback);
            if (cb != null) {
                return;
            }
            cb = new IAppOpsNotedCallback.Stub() {
                @Override
                public void opNoted(int op, int uid, String packageName, int mode) {
                    callback.onOpNoted(sOpToString[op], uid, packageName, mode);
                }
            };
            mNotedWatchers.put(callback, cb);
        }
        try {
            final int[] opCodes = new int[ops.length];
            for (int i = 0; i < opCodes.length; i++) {
                opCodes[i] = strOpToOp(ops[i]);
            }
            mService.startWatchingNoted(opCodes, cb);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Stop watching for noted app ops. An app op may be immediate or long running.
     * Unregistering a non-registered callback has no effect.
     *
     * @see #startWatchingNoted(String[], OnOpNotedListener)
     * @see #noteOp(String, int, String)
     *
     * @hide
     */
    public void stopWatchingNoted(@NonNull OnOpNotedListener callback) {
        synchronized (mNotedWatchers) {
            final IAppOpsNotedCallback cb = mNotedWatchers.get(callback);
            if (cb != null) {
                try {
                    mService.stopWatchingNoted(cb);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
        }
    }

    private String buildSecurityExceptionMsg(int op, int uid, String packageName) {
        return packageName + " from uid " + uid + " not allowed to perform " + sOpNames[op];
    }
+22 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.internal.app;

// Iterface to observe op note/checks of ops
oneway interface IAppOpsNotedCallback {
    void opNoted(int op, int uid, String packageName, int mode);
}
+4 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.content.pm.ParceledListSlice;
import android.os.Bundle;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsActiveCallback;
import com.android.internal.app.IAppOpsNotedCallback;

interface IAppOpsService {
    // These first methods are also called by native code, so must
@@ -61,4 +62,7 @@ interface IAppOpsService {
    boolean isOperationActive(int code, int uid, String packageName);

    void startWatchingModeWithFlags(int op, String packageName, int flags, IAppOpsCallback callback);

    void startWatchingNoted(in int[] ops, IAppOpsNotedCallback callback);
    void stopWatchingNoted(IAppOpsNotedCallback callback);
}
+186 −5
Original line number Diff line number Diff line
@@ -86,6 +86,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IAppOpsActiveCallback;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsNotedCallback;
import com.android.internal.app.IAppOpsService;
import com.android.internal.os.Zygote;
import com.android.internal.util.ArrayUtils;
@@ -456,6 +457,7 @@ public class AppOpsService extends IAppOpsService.Stub {
    final ArrayMap<String, ArraySet<ModeCallback>> mPackageModeWatchers = new ArrayMap<>();
    final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>();
    final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>();
    final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>();
    final SparseArray<SparseArray<Restriction>> mAudioRestrictions = new SparseArray<>();

    final class ModeCallback implements DeathRecipient {
@@ -475,6 +477,7 @@ public class AppOpsService extends IAppOpsService.Stub {
            try {
                mCallback.asBinder().linkToDeath(this, 0);
            } catch (RemoteException e) {
                /*ignored*/
            }
        }

@@ -524,6 +527,7 @@ public class AppOpsService extends IAppOpsService.Stub {
            try {
                mCallback.asBinder().linkToDeath(this, 0);
            } catch (RemoteException e) {
                /*ignored*/
            }
        }

@@ -552,6 +556,50 @@ public class AppOpsService extends IAppOpsService.Stub {
        }
    }

    final class NotedCallback implements DeathRecipient {
        final IAppOpsNotedCallback mCallback;
        final int mWatchingUid;
        final int mCallingUid;
        final int mCallingPid;

        NotedCallback(IAppOpsNotedCallback callback, int watchingUid, int callingUid,
                int callingPid) {
            mCallback = callback;
            mWatchingUid = watchingUid;
            mCallingUid = callingUid;
            mCallingPid = callingPid;
            try {
                mCallback.asBinder().linkToDeath(this, 0);
            } catch (RemoteException e) {
                /*ignored*/
            }
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder(128);
            sb.append("NotedCallback{");
            sb.append(Integer.toHexString(System.identityHashCode(this)));
            sb.append(" watchinguid=");
            UserHandle.formatUid(sb, mWatchingUid);
            sb.append(" from uid=");
            UserHandle.formatUid(sb, mCallingUid);
            sb.append(" pid=");
            sb.append(mCallingPid);
            sb.append('}');
            return sb.toString();
        }

        void destroy() {
            mCallback.asBinder().unlinkToDeath(this, 0);
        }

        @Override
        public void binderDied() {
            stopWatchingNoted(mCallback);
        }
    }

    final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<>();

    final class ClientState extends Binder implements DeathRecipient {
@@ -1629,7 +1677,7 @@ public class AppOpsService extends IAppOpsService.Stub {
            UidState uidState = getUidStateLocked(uid, false);
            if (uidState != null && uidState.opModes != null
                    && uidState.opModes.indexOfKey(code) >= 0) {
                return uidState.opModes.get(code);
                return uidState.evalMode(uidState.opModes.get(code));
            }
            Op op = getOpLocked(code, uid, packageName, false, true, false);
            if (op == null) {
@@ -1795,12 +1843,16 @@ public class AppOpsService extends IAppOpsService.Stub {
            final Ops ops = getOpsRawLocked(uid, packageName, true /* edit */,
                    false /* uidMismatchExpected */);
            if (ops == null) {
                scheduleOpNotedIfNeededLocked(code, uid, packageName,
                        AppOpsManager.MODE_IGNORED);
                if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
                        + " package " + packageName);
                return AppOpsManager.MODE_ERRORED;
            }
            final Op op = getOpLocked(ops, code, true);
            if (isOpRestrictedLocked(uid, code, packageName)) {
                scheduleOpNotedIfNeededLocked(code, uid, packageName,
                        AppOpsManager.MODE_IGNORED);
                return AppOpsManager.MODE_IGNORED;
            }
            final UidState uidState = ops.uidState;
@@ -1820,6 +1872,7 @@ public class AppOpsService extends IAppOpsService.Stub {
                            + switchCode + " (" + code + ") uid " + uid + " package "
                            + packageName);
                    op.rejectTime[uidState.state] = System.currentTimeMillis();
                    scheduleOpNotedIfNeededLocked(code, uid, packageName, uidMode);
                    return uidMode;
                }
            } else {
@@ -1830,6 +1883,7 @@ public class AppOpsService extends IAppOpsService.Stub {
                            + switchCode + " (" + code + ") uid " + uid + " package "
                            + packageName);
                    op.rejectTime[uidState.state] = System.currentTimeMillis();
                    scheduleOpNotedIfNeededLocked(op.op, uid, packageName, mode);
                    return mode;
                }
            }
@@ -1839,6 +1893,8 @@ public class AppOpsService extends IAppOpsService.Stub {
            op.rejectTime[uidState.state] = 0;
            op.proxyUid = proxyUid;
            op.proxyPackageName = proxyPackageName;
            scheduleOpNotedIfNeededLocked(code, uid, packageName,
                    AppOpsManager.MODE_ALLOWED);
            return AppOpsManager.MODE_ALLOWED;
        }
    }
@@ -1886,12 +1942,52 @@ public class AppOpsService extends IAppOpsService.Stub {
            }
            final int callbackCount = activeCallbacks.size();
            for (int i = 0; i < callbackCount; i++) {
                // Apps ops are mapped to a singleton
                if (i == 0) {
                activeCallbacks.valueAt(i).destroy();
            }
        }
    }

    @Override
    public void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback) {
        int watchedUid = Process.INVALID_UID;
        final int callingUid = Binder.getCallingUid();
        final int callingPid = Binder.getCallingPid();
        if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
                != PackageManager.PERMISSION_GRANTED) {
            watchedUid = callingUid;
        }
        Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
        Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
                "Invalid op code in: " + Arrays.toString(ops));
        Preconditions.checkNotNull(callback, "Callback cannot be null");
        synchronized (this) {
            SparseArray<NotedCallback> callbacks = mNotedWatchers.get(callback.asBinder());
            if (callbacks == null) {
                callbacks = new SparseArray<>();
                mNotedWatchers.put(callback.asBinder(), callbacks);
            }
            final NotedCallback notedCallback = new NotedCallback(callback, watchedUid,
                    callingUid, callingPid);
            for (int op : ops) {
                callbacks.put(op, notedCallback);
            }
        }
    }

    @Override
    public void stopWatchingNoted(IAppOpsNotedCallback callback) {
        Preconditions.checkNotNull(callback, "Callback cannot be null");
        synchronized (this) {
            final SparseArray<NotedCallback> notedCallbacks =
                    mNotedWatchers.remove(callback.asBinder());
            if (notedCallbacks == null) {
                return;
            }
            final int callbackCount = notedCallbacks.size();
            for (int i = 0; i < callbackCount; i++) {
                notedCallbacks.valueAt(i).destroy();
            }
        }
    }

    @Override
@@ -2052,6 +2148,51 @@ public class AppOpsService extends IAppOpsService.Stub {
        }
    }

    private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName,
            int result) {
        ArraySet<NotedCallback> dispatchedCallbacks = null;
        final int callbackListCount = mNotedWatchers.size();
        for (int i = 0; i < callbackListCount; i++) {
            final SparseArray<NotedCallback> callbacks = mNotedWatchers.valueAt(i);
            final NotedCallback callback = callbacks.get(code);
            if (callback != null) {
                if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
                    continue;
                }
                if (dispatchedCallbacks == null) {
                    dispatchedCallbacks = new ArraySet<>();
                }
                dispatchedCallbacks.add(callback);
            }
        }
        if (dispatchedCallbacks == null) {
            return;
        }
        mHandler.sendMessage(PooledLambda.obtainMessage(
                AppOpsService::notifyOpChecked,
                this, dispatchedCallbacks, code, uid, packageName, result));
    }

    private void notifyOpChecked(ArraySet<NotedCallback> callbacks,
            int code, int uid, String packageName, int result) {
        // There are components watching for checks in our process. The callbacks in
        // these components may require permissions our remote caller does not have.
        final long identity = Binder.clearCallingIdentity();
        try {
            final int callbackCount = callbacks.size();
            for (int i = 0; i < callbackCount; i++) {
                final NotedCallback callback = callbacks.valueAt(i);
                try {
                    callback.mCallback.opNoted(code, uid, packageName, result);
                } catch (RemoteException e) {
                    /* do nothing */
                }
            }
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    @Override
    public int permissionToOpCode(String permission) {
        if (permission == null) {
@@ -3463,6 +3604,46 @@ public class AppOpsService extends IAppOpsService.Stub {
                    pw.println(cb);
                }
            }
            if (mNotedWatchers.size() > 0 && dumpMode < 0) {
                needSep = true;
                boolean printedHeader = false;
                for (int i = 0; i < mNotedWatchers.size(); i++) {
                    final SparseArray<NotedCallback> notedWatchers = mNotedWatchers.valueAt(i);
                    if (notedWatchers.size() <= 0) {
                        continue;
                    }
                    final NotedCallback cb = notedWatchers.valueAt(0);
                    if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) {
                        continue;
                    }
                    if (dumpPackage != null && cb.mWatchingUid >= 0
                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
                        continue;
                    }
                    if (!printedHeader) {
                        pw.println("  All op noted watchers:");
                        printedHeader = true;
                    }
                    pw.print("    ");
                    pw.print(Integer.toHexString(System.identityHashCode(
                            mNotedWatchers.keyAt(i))));
                    pw.println(" ->");
                    pw.print("        [");
                    final int opCount = notedWatchers.size();
                    for (i = 0; i < opCount; i++) {
                        if (i > 0) {
                            pw.print(' ');
                        }
                        pw.print(AppOpsManager.opToName(notedWatchers.keyAt(i)));
                        if (i < opCount - 1) {
                            pw.print(',');
                        }
                    }
                    pw.println("]");
                    pw.print("        ");
                    pw.println(cb);
                }
            }
            if (mClients.size() > 0 && dumpMode < 0) {
                needSep = true;
                boolean printedHeader = false;
Loading