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

Commit 7095ab9f authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Public API to watch for "active" operations.

We already have startOp() and finishOp() to provide a mechanism to
mark an operation as being "active".  This change publishes a set of
APIs to check the status of these active operations, matching the
naming of similar existing APIs on the same class.

Bug: 139128842
Test: atest com.android.server.appop.AppOpsActiveWatcherTest
Test: atest android.media.cts.AudioRecordAppOpTest
Exempt-From-Owner-Approval: trivial API refactoring
Change-Id: I9fb381d748ff23ff24dd363ed5b117bd661793ab
parent 570926da
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -4278,6 +4278,7 @@ package android.app {
    method @Deprecated public int checkOpNoThrow(@NonNull String, int, @NonNull String);
    method public void checkPackage(int, @NonNull String);
    method public void finishOp(@NonNull String, int, @NonNull String);
    method public boolean isOpActive(@NonNull String, int, @NonNull String);
    method public int noteOp(@NonNull String, int, @NonNull String);
    method public int noteOpNoThrow(@NonNull String, int, @NonNull String);
    method public int noteProxyOp(@NonNull String, @NonNull String);
@@ -4286,8 +4287,10 @@ package android.app {
    method public static String permissionToOp(String);
    method public int startOp(@NonNull String, int, @NonNull String);
    method public int startOpNoThrow(@NonNull String, int, @NonNull String);
    method public void startWatchingActive(@NonNull String[], @NonNull java.util.concurrent.Executor, @NonNull android.app.AppOpsManager.OnOpActiveChangedListener);
    method public void startWatchingMode(@NonNull String, @Nullable String, @NonNull android.app.AppOpsManager.OnOpChangedListener);
    method public void startWatchingMode(@NonNull String, @Nullable String, int, @NonNull android.app.AppOpsManager.OnOpChangedListener);
    method public void stopWatchingActive(@NonNull android.app.AppOpsManager.OnOpActiveChangedListener);
    method public void stopWatchingMode(@NonNull android.app.AppOpsManager.OnOpChangedListener);
    method public int unsafeCheckOp(@NonNull String, int, @NonNull String);
    method public int unsafeCheckOpNoThrow(@NonNull String, int, @NonNull String);
@@ -4335,6 +4338,10 @@ package android.app {
    field public static final int WATCH_FOREGROUND_CHANGES = 1; // 0x1
  }
  public static interface AppOpsManager.OnOpActiveChangedListener {
    method public void onOpActiveChanged(@NonNull String, int, @NonNull String, boolean);
  }
  public static interface AppOpsManager.OnOpChangedListener {
    method public void onOpChanged(String, String);
  }
+0 −6
Original line number Diff line number Diff line
@@ -160,8 +160,6 @@ package android.app {
    method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(int, int, String, int);
    method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(String, int, String, int);
    method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setUidMode(String, int, int);
    method public void startWatchingActive(@NonNull int[], @NonNull android.app.AppOpsManager.OnOpActiveChangedListener);
    method public void stopWatchingActive(@NonNull android.app.AppOpsManager.OnOpActiveChangedListener);
    method public static int strOpToOp(@NonNull String);
    field public static final int HISTORICAL_MODE_DISABLED = 0; // 0x0
    field public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1; // 0x1
@@ -293,10 +291,6 @@ package android.app {
    field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalUidOps> CREATOR;
  }

  public static interface AppOpsManager.OnOpActiveChangedListener {
    method public void onOpActiveChanged(int, int, String, boolean);
  }

  public static final class AppOpsManager.OpEntry implements android.os.Parcelable {
    method public int describeContents();
    method public long getDuration();
+64 −23
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.app;

import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -29,6 +30,7 @@ import android.annotation.UnsupportedAppUsage;
import android.app.usage.UsageStatsManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ParceledListSlice;
import android.media.AudioAttributes.AttributeUsage;
import android.os.Binder;
@@ -4281,20 +4283,17 @@ public class AppOpsManager {

    /**
     * Callback for notification of changes to operation active state.
     *
     * @hide
     */
    @TestApi
    public interface OnOpActiveChangedListener {
        /**
         * Called when the active state of an app op changes.
         *
         * @param code The op code.
         * @param uid The UID performing the operation.
         * @param op The operation that changed.
         * @param packageName The package performing the operation.
         * @param active Whether the operation became active or inactive.
         */
        void onOpActiveChanged(int code, int uid, String packageName, boolean active);
        void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName,
                boolean active);
    }

    /**
@@ -4324,6 +4323,16 @@ public class AppOpsManager {
        public void onOpChanged(int op, String packageName) { }
    }

    /**
     * Callback for notification of changes to operation state.
     * This allows you to see the raw op codes instead of strings.
     * @hide
     */
    public interface OnOpActiveChangedInternalListener extends OnOpActiveChangedListener {
        default void onOpActiveChanged(String op, int uid, String packageName, boolean active) { }
        default void onOpActiveChanged(int op, int uid, String packageName, boolean active) { }
    }

    AppOpsManager(Context context, IAppOpsService service) {
        mContext = context;
        mService = service;
@@ -4779,6 +4788,17 @@ public class AppOpsManager {
        }
    }

    /** {@hide} */
    @Deprecated
    public void startWatchingActive(@NonNull int[] ops,
            @NonNull OnOpActiveChangedListener callback) {
        final String[] strOps = new String[ops.length];
        for (int i = 0; i < ops.length; i++) {
            strOps[i] = opToPublicName(ops[i]);
        }
        startWatchingActive(strOps, mContext.getMainExecutor(), callback);
    }

    /**
     * Start watching for changes to the active state of app ops. An app op may be
     * long running and it has a clear start and stop delimiters. If an op is being
@@ -4786,26 +4806,25 @@ public class AppOpsManager {
     * 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
     * <p> If you don't hold the {@code android.Manifest.permission#WATCH_APPOPS} permission
     * you can watch changes only for your UID.
     *
     * @param ops The ops to watch.
     * @param ops The operations to watch.
     * @param callback Where to report changes.
     *
     * @see #isOperationActive(int, int, String)
     * @see #stopWatchingActive(OnOpActiveChangedListener)
     * @see #isOperationActive
     * @see #stopWatchingActive
     * @see #startOp(int, int, String)
     * @see #finishOp(int, int, String)
     *
     * @hide
     */
    @TestApi
    // TODO: Uncomment below annotation once b/73559440 is fixed
    // @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true)
    public void startWatchingActive(@NonNull int[] ops,
    public void startWatchingActive(@NonNull String[] ops,
            @CallbackExecutor @NonNull Executor executor,
            @NonNull OnOpActiveChangedListener callback) {
        Preconditions.checkNotNull(ops, "ops cannot be null");
        Preconditions.checkNotNull(callback, "callback cannot be null");
        Objects.requireNonNull(ops);
        Objects.requireNonNull(executor);
        Objects.requireNonNull(callback);
        IAppOpsActiveCallback cb;
        synchronized (mActiveWatchers) {
            cb = mActiveWatchers.get(callback);
@@ -4815,13 +4834,25 @@ public class AppOpsManager {
            cb = new IAppOpsActiveCallback.Stub() {
                @Override
                public void opActiveChanged(int op, int uid, String packageName, boolean active) {
                    callback.onOpActiveChanged(op, uid, packageName, active);
                    executor.execute(() -> {
                        if (callback instanceof OnOpActiveChangedInternalListener) {
                            ((OnOpActiveChangedInternalListener) callback).onOpActiveChanged(op,
                                    uid, packageName, active);
                        }
                        if (sOpToString[op] != null) {
                            callback.onOpActiveChanged(sOpToString[op], uid, packageName, active);
                        }
                    });
                }
            };
            mActiveWatchers.put(callback, cb);
        }
        final int[] rawOps = new int[ops.length];
        for (int i = 0; i < ops.length; i++) {
            rawOps[i] = strOpToOp(ops[i]);
        }
        try {
            mService.startWatchingActive(ops, cb);
            mService.startWatchingActive(rawOps, cb);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
@@ -4832,14 +4863,11 @@ public class AppOpsManager {
     * long running and it has a clear start and stop delimiters. Unregistering a
     * non-registered callback has no effect.
     *
     * @see #isOperationActive#(int, int, String)
     * @see #startWatchingActive(int[], OnOpActiveChangedListener)
     * @see #isOperationActive
     * @see #startWatchingActive
     * @see #startOp(int, int, String)
     * @see #finishOp(int, int, String)
     *
     * @hide
     */
    @TestApi
    public void stopWatchingActive(@NonNull OnOpActiveChangedListener callback) {
        synchronized (mActiveWatchers) {
            final IAppOpsActiveCallback cb = mActiveWatchers.remove(callback);
@@ -5448,6 +5476,19 @@ public class AppOpsManager {
        finishOp(op, Process.myUid(), mContext.getOpPackageName());
    }

    /**
     * Checks whether the given op for a package is active.
     * <p>
     * If you don't hold the {@code android.Manifest.permission#WATCH_APPOPS}
     * permission you can query only for your UID.
     *
     * @see #finishOp(int)
     * @see #startOp(int)
     */
    public boolean isOpActive(@NonNull String op, int uid, @NonNull String packageName) {
        return isOperationActive(strOpToOp(op), uid, packageName);
    }

    /**
     * Checks whether the given op for a UID and package is active.
     *
+1 −1
Original line number Diff line number Diff line
@@ -50,7 +50,7 @@ import javax.inject.Singleton;
 */
@Singleton
public class AppOpsControllerImpl implements AppOpsController,
        AppOpsManager.OnOpActiveChangedListener,
        AppOpsManager.OnOpActiveChangedInternalListener,
        AppOpsManager.OnOpNotedListener, Dumpable {

    private static final long NOTED_OP_TIME_DELAY_MS = 5000;
+5 −5
Original line number Diff line number Diff line
@@ -57,15 +57,15 @@ public class AppOpsActiveWatcherTest {

        // Start watching active ops
        final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
        appOpsManager.startWatchingActive(new int[] {AppOpsManager.OP_CAMERA,
                AppOpsManager.OP_RECORD_AUDIO}, listener);
        appOpsManager.startWatchingActive(new String[] {AppOpsManager.OPSTR_CAMERA,
                AppOpsManager.OPSTR_RECORD_AUDIO}, getContext().getMainExecutor(), listener);

        // Start the op
        appOpsManager.startOp(AppOpsManager.OP_CAMERA);

        // Verify that we got called for the op being active
        verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
                .times(1)).onOpActiveChanged(eq(AppOpsManager.OP_CAMERA),
                .times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
                eq(Process.myUid()), eq(getContext().getPackageName()), eq(true));

        // This should be the only callback we got
@@ -83,7 +83,7 @@ public class AppOpsActiveWatcherTest {

        // Verify that we got called for the op being active
        verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
                .times(1)).onOpActiveChanged(eq(AppOpsManager.OP_CAMERA),
                .times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
                eq(Process.myUid()), eq(getContext().getPackageName()), eq(false));

        // Verify that the op is not active