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

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

Merge "Allow apps to bulk revoke permissions with the correct semantics"

parents 332b7d09 7868952d
Loading
Loading
Loading
Loading
+12 −0
Original line number Original line Diff line number Diff line
@@ -4599,6 +4599,17 @@ package android.os.storage {


package android.permission {
package android.permission {


  public final class PermissionControllerManager {
    method public void revokeRuntimePermissions(java.util.Map<java.lang.String, java.util.List<java.lang.String>>, boolean, int, java.util.concurrent.Executor, android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback);
    field public static final int REASON_INSTALLER_POLICY_VIOLATION = 2; // 0x2
    field public static final int REASON_MALWARE = 1; // 0x1
  }

  public static abstract class PermissionControllerManager.OnRevokeRuntimePermissionsCallback {
    ctor public PermissionControllerManager.OnRevokeRuntimePermissionsCallback();
    method public abstract void onRevokeRuntimePermissions(java.util.Map<java.lang.String, java.util.List<java.lang.String>>);
  }

  public abstract class PermissionControllerService extends android.app.Service {
  public abstract class PermissionControllerService extends android.app.Service {
    ctor public PermissionControllerService();
    ctor public PermissionControllerService();
    method public final void attachBaseContext(android.content.Context);
    method public final void attachBaseContext(android.content.Context);
@@ -4606,6 +4617,7 @@ package android.permission {
    method public abstract int onCountPermissionApps(java.util.List<java.lang.String>, boolean, boolean);
    method public abstract int onCountPermissionApps(java.util.List<java.lang.String>, boolean, boolean);
    method public abstract java.util.List<android.permission.RuntimePermissionPresentationInfo> onGetAppPermissions(java.lang.String);
    method public abstract java.util.List<android.permission.RuntimePermissionPresentationInfo> onGetAppPermissions(java.lang.String);
    method public abstract void onRevokeRuntimePermission(java.lang.String, java.lang.String);
    method public abstract void onRevokeRuntimePermission(java.lang.String, java.lang.String);
    method public abstract java.util.Map<java.lang.String, java.util.List<java.lang.String>> onRevokeRuntimePermissions(java.util.Map<java.lang.String, java.util.List<java.lang.String>>, boolean, int, java.lang.String);
    field public static final java.lang.String SERVICE_INTERFACE = "android.permission.PermissionControllerService";
    field public static final java.lang.String SERVICE_INTERFACE = "android.permission.PermissionControllerService";
  }
  }


+15 −0
Original line number Original line Diff line number Diff line
@@ -985,6 +985,21 @@ package android.os.strictmode {


}
}


package android.permission {

  public final class PermissionControllerManager {
    method public void revokeRuntimePermissions(java.util.Map<java.lang.String, java.util.List<java.lang.String>>, boolean, int, java.util.concurrent.Executor, android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback);
    field public static final int REASON_INSTALLER_POLICY_VIOLATION = 2; // 0x2
    field public static final int REASON_MALWARE = 1; // 0x1
  }

  public static abstract class PermissionControllerManager.OnRevokeRuntimePermissionsCallback {
    ctor public PermissionControllerManager.OnRevokeRuntimePermissionsCallback();
    method public abstract void onRevokeRuntimePermissions(java.util.Map<java.lang.String, java.util.List<java.lang.String>>);
  }

}

package android.print {
package android.print {


  public final class PrintJobInfo implements android.os.Parcelable {
  public final class PrintJobInfo implements android.os.Parcelable {
+3 −0
Original line number Original line Diff line number Diff line
@@ -17,6 +17,7 @@
package android.permission;
package android.permission;


import android.os.RemoteCallback;
import android.os.RemoteCallback;
import android.os.Bundle;


/**
/**
 * Interface for system apps to communication with the permission controller.
 * Interface for system apps to communication with the permission controller.
@@ -24,6 +25,8 @@ import android.os.RemoteCallback;
 * @hide
 * @hide
 */
 */
oneway interface IPermissionController {
oneway interface IPermissionController {
    void revokeRuntimePermissions(in Bundle request, boolean doDryRun, int reason,
            String callerPackageName, in RemoteCallback callback);
    void getAppPermissions(String packageName, in RemoteCallback callback);
    void getAppPermissions(String packageName, in RemoteCallback callback);
    void revokeRuntimePermission(String packageName, String permissionName);
    void revokeRuntimePermission(String packageName, String permissionName);
    void countPermissionApps(in List<String> permissionNames, boolean countOnlyGranted,
    void countPermissionApps(in List<String> permissionNames, boolean countOnlyGranted,
+176 −3
Original line number Original line Diff line number Diff line
@@ -22,46 +22,97 @@ import static com.android.internal.util.Preconditions.checkCollectionElementsNot
import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.internal.util.Preconditions.checkNotNull;


import android.Manifest;
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.ComponentName;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ResolveInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder;
import android.os.RemoteCallback;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Log;


import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
import com.android.internal.infra.AbstractRemoteService;
import com.android.internal.infra.AbstractRemoteService;
import com.android.internal.util.Preconditions;


import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Collections;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;


/**
/**
 * Interface for communicating with the permission controller from system apps. All UI operations
 * Interface for communicating with the permission controller.
 * regarding permissions and any changes to the permission state should flow through this
 * interface.
 *
 *
 * @hide
 * @hide
 */
 */
@TestApi
@SystemApi
@SystemService(Context.PERMISSION_CONTROLLER_SERVICE)
@SystemService(Context.PERMISSION_CONTROLLER_SERVICE)
public final class PermissionControllerManager {
public final class PermissionControllerManager {
    private static final String TAG = PermissionControllerManager.class.getSimpleName();
    private static final String TAG = PermissionControllerManager.class.getSimpleName();


    /**
    /**
     * The key for retrieving the result from the returned bundle.
     * The key for retrieving the result from the returned bundle.
     *
     * @hide
     */
     */
    public static final String KEY_RESULT =
    public static final String KEY_RESULT =
            "android.permission.PermissionControllerManager.key.result";
            "android.permission.PermissionControllerManager.key.result";


    /** @hide */
    @IntDef(prefix = { "REASON_" }, value = {
            REASON_MALWARE,
            REASON_INSTALLER_POLICY_VIOLATION,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface Reason {}

    /** The permissions are revoked because the apps holding the permissions are malware */
    public static final int REASON_MALWARE = 1;

    /**
     * The permissions are revoked because the apps holding the permissions violate a policy of the
     * app that installed it.
     *
     * <p>If this reason is used only permissions of apps that are installed by the caller of the
     * API can be revoked.
     */
    public static final int REASON_INSTALLER_POLICY_VIOLATION = 2;

    /**
     * Callback for delivering the result of {@link #revokeRuntimePermissions}.
     */
    public abstract static class OnRevokeRuntimePermissionsCallback {
        /**
         * The result for {@link #revokeRuntimePermissions}.
         *
         * @param revoked The actually revoked permissions as
         *                {@code Map<packageName, List<permission>>}
         */
        public abstract void onRevokeRuntimePermissions(@NonNull Map<String, List<String>> revoked);
    }

    /**
    /**
     * Callback for delivering the result of {@link #getAppPermissions}.
     * Callback for delivering the result of {@link #getAppPermissions}.
     *
     * @hide
     */
     */
    public interface OnGetAppPermissionResultCallback {
    public interface OnGetAppPermissionResultCallback {
        /**
        /**
@@ -75,6 +126,8 @@ public final class PermissionControllerManager {


    /**
    /**
     * Callback for delivering the result of {@link #countPermissionApps}.
     * Callback for delivering the result of {@link #countPermissionApps}.
     *
     * @hide
     */
     */
    public interface OnCountPermissionAppsResultCallback {
    public interface OnCountPermissionAppsResultCallback {
        /**
        /**
@@ -86,23 +139,61 @@ public final class PermissionControllerManager {
        void onCountPermissionApps(int numApps);
        void onCountPermissionApps(int numApps);
    }
    }


    private final @NonNull Context mContext;
    private final RemoteService mRemoteService;
    private final RemoteService mRemoteService;


    /** @hide */
    public PermissionControllerManager(@NonNull Context context) {
    public PermissionControllerManager(@NonNull Context context) {
        Intent intent = new Intent(SERVICE_INTERFACE);
        Intent intent = new Intent(SERVICE_INTERFACE);
        intent.setPackage(context.getPackageManager().getPermissionControllerPackageName());
        intent.setPackage(context.getPackageManager().getPermissionControllerPackageName());
        ResolveInfo serviceInfo = context.getPackageManager().resolveService(intent, 0);
        ResolveInfo serviceInfo = context.getPackageManager().resolveService(intent, 0);


        mContext = context;
        mRemoteService = new RemoteService(context,
        mRemoteService = new RemoteService(context,
                serviceInfo.getComponentInfo().getComponentName());
                serviceInfo.getComponentInfo().getComponentName());
    }
    }


    /**
     * Revoke a set of runtime permissions for various apps.
     *
     * @param request The permissions to revoke as {@code Map<packageName, List<permission>>}
     * @param doDryRun Compute the permissions that would be revoked, but not actually revoke them
     * @param reason Why the permission should be revoked
     * @param executor Executor on which to invoke the callback
     * @param callback Callback to receive the result
     */
    @RequiresPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
    public void revokeRuntimePermissions(@NonNull Map<String, List<String>> request,
            boolean doDryRun, @Reason int reason, @NonNull @CallbackExecutor Executor executor,
            @NonNull OnRevokeRuntimePermissionsCallback callback) {
        // Check input to fail immediately instead of inside the async request
        checkNotNull(executor);
        checkNotNull(callback);
        checkNotNull(request);
        for (Map.Entry<String, List<String>> appRequest : request.entrySet()) {
            checkNotNull(appRequest.getKey());
            checkCollectionElementsNotNull(appRequest.getValue(), "permissions");
        }

        // Check required permission to fail immediately instead of inside the oneway binder call
        if (mContext.checkSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
                != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
                    + " required");
        }

        mRemoteService.scheduleRequest(new PendingRevokeRuntimePermissionRequest(mRemoteService,
                request, doDryRun, reason, mContext.getPackageName(), executor, callback));
    }

    /**
    /**
     * Gets the runtime permissions for an app.
     * Gets the runtime permissions for an app.
     *
     *
     * @param packageName The package for which to query.
     * @param packageName The package for which to query.
     * @param callback Callback to receive the result.
     * @param callback Callback to receive the result.
     * @param handler Handler on which to invoke the callback.
     * @param handler Handler on which to invoke the callback.
     *
     * @hide
     */
     */
    @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
    @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
    public void getAppPermissions(@NonNull String packageName,
    public void getAppPermissions(@NonNull String packageName,
@@ -119,6 +210,8 @@ public final class PermissionControllerManager {
     *
     *
     * @param packageName The package for which to revoke
     * @param packageName The package for which to revoke
     * @param permissionName The permission to revoke
     * @param permissionName The permission to revoke
     *
     * @hide
     */
     */
    @RequiresPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
    @RequiresPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
    public void revokeRuntimePermission(@NonNull String packageName,
    public void revokeRuntimePermission(@NonNull String packageName,
@@ -138,6 +231,8 @@ public final class PermissionControllerManager {
     * @param countSystem Also count system apps
     * @param countSystem Also count system apps
     * @param callback Callback to receive the result
     * @param callback Callback to receive the result
     * @param handler Handler on which to invoke the callback
     * @param handler Handler on which to invoke the callback
     *
     * @hide
     */
     */
    @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
    @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
    public void countPermissionApps(@NonNull List<String> permissionNames,
    public void countPermissionApps(@NonNull List<String> permissionNames,
@@ -205,6 +300,84 @@ public final class PermissionControllerManager {
        }
        }
    }
    }


    /**
     * Request for {@link #revokeRuntimePermissions}
     */
    private static final class PendingRevokeRuntimePermissionRequest extends
            AbstractRemoteService.PendingRequest<RemoteService, IPermissionController> {
        private final @NonNull Map<String, List<String>> mRequest;
        private final boolean mDoDryRun;
        private final int mReason;
        private final @NonNull String mCallingPackage;
        private final @NonNull OnRevokeRuntimePermissionsCallback mCallback;

        private final @NonNull RemoteCallback mRemoteCallback;

        private PendingRevokeRuntimePermissionRequest(@NonNull RemoteService service,
                @NonNull Map<String, List<String>> request, boolean doDryRun,
                @Reason int reason, @NonNull String callingPackage,
                @NonNull @CallbackExecutor Executor executor,
                @NonNull OnRevokeRuntimePermissionsCallback callback) {
            super(service);

            mRequest = request;
            mDoDryRun = doDryRun;
            mReason = reason;
            mCallingPackage = callingPackage;
            mCallback = callback;

            mRemoteCallback = new RemoteCallback(result -> executor.execute(() -> {
                long token = Binder.clearCallingIdentity();
                try {
                    Map<String, List<String>> revoked = new ArrayMap<>();
                    try {
                        Bundle bundleizedRevoked = result.getBundle(KEY_RESULT);

                        for (String packageName : bundleizedRevoked.keySet()) {
                            Preconditions.checkNotNull(packageName);

                            ArrayList<String> permissions =
                                    bundleizedRevoked.getStringArrayList(packageName);
                            Preconditions.checkCollectionElementsNotNull(permissions,
                                    "permissions");

                            revoked.put(packageName, permissions);
                        }
                    } catch (Exception e) {
                        Log.e(TAG, "Could not read result when revoking runtime permissions", e);
                    }

                    callback.onRevokeRuntimePermissions(revoked);
                } finally {
                    Binder.restoreCallingIdentity(token);

                    finish();
                }
            }), null);
        }

        @Override
        protected void onTimeout(RemoteService remoteService) {
            mCallback.onRevokeRuntimePermissions(Collections.emptyMap());
        }

        @Override
        public void run() {
            Bundle bundledizedRequest = new Bundle();
            for (Map.Entry<String, List<String>> appRequest : mRequest.entrySet()) {
                bundledizedRequest.putStringArrayList(appRequest.getKey(),
                        new ArrayList<>(appRequest.getValue()));
            }

            try {
                getService().getServiceInterface().revokeRuntimePermissions(bundledizedRequest,
                        mDoDryRun, mReason, mCallingPackage, mRemoteCallback);
            } catch (RemoteException e) {
                Log.e(TAG, "Error revoking runtime permission", e);
            }
        }
    }

    /**
    /**
     * Request for {@link #getAppPermissions}
     * Request for {@link #getAppPermissions}
     */
     */
+78 −0
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@


package android.permission;
package android.permission;


import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
@@ -26,12 +27,19 @@ import android.annotation.SystemApi;
import android.app.Service;
import android.app.Service;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Bundle;
import android.os.Handler;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder;
import android.os.RemoteCallback;
import android.os.RemoteCallback;
import android.util.ArrayMap;


import com.android.internal.util.Preconditions;

import java.util.ArrayList;
import java.util.List;
import java.util.List;
import java.util.Map;


/**
/**
 * This service is meant to be implemented by the app controlling permissions.
 * This service is meant to be implemented by the app controlling permissions.
@@ -59,6 +67,20 @@ public abstract class PermissionControllerService extends Service {
        mHandler = new Handler(base.getMainLooper());
        mHandler = new Handler(base.getMainLooper());
    }
    }


    /**
     * Revoke a set of runtime permissions for various apps.
     *
     * @param requests The permissions to revoke as {@code Map<packageName, List<permission>>}
     * @param doDryRun Compute the permissions that would be revoked, but not actually revoke them
     * @param reason Why the permission should be revoked
     * @param callerPackageName The package name of the calling app
     *
     * @return the actually removed permissions as {@code Map<packageName, List<permission>>}
     */
    public abstract @NonNull Map<String, List<String>> onRevokeRuntimePermissions(
            @NonNull Map<String, List<String>> requests, boolean doDryRun,
            @PermissionControllerManager.Reason int reason, @NonNull String callerPackageName);

    /**
    /**
     * Gets the runtime permissions for an app.
     * Gets the runtime permissions for an app.
     *
     *
@@ -93,6 +115,41 @@ public abstract class PermissionControllerService extends Service {
    @Override
    @Override
    public final IBinder onBind(Intent intent) {
    public final IBinder onBind(Intent intent) {
        return new IPermissionController.Stub() {
        return new IPermissionController.Stub() {
            @Override
            public void revokeRuntimePermissions(
                    Bundle bundleizedRequest, boolean doDryRun, int reason,
                    String callerPackageName, RemoteCallback callback) {
                checkNotNull(bundleizedRequest, "bundleizedRequest");
                checkNotNull(callerPackageName);
                checkNotNull(callback);

                Map<String, List<String>> request = new ArrayMap<>();
                for (String packageName : bundleizedRequest.keySet()) {
                    Preconditions.checkNotNull(packageName);

                    ArrayList<String> permissions =
                            bundleizedRequest.getStringArrayList(packageName);
                    Preconditions.checkCollectionElementsNotNull(permissions, "permissions");

                    request.put(packageName, permissions);
                }

                enforceCallingPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, null);

                // Verify callerPackageName
                try {
                    PackageInfo pkgInfo = getPackageManager().getPackageInfo(callerPackageName, 0);
                    checkArgument(getCallingUid() == pkgInfo.applicationInfo.uid);
                } catch (PackageManager.NameNotFoundException e) {
                    throw new RuntimeException(e);
                }

                mHandler.sendMessage(obtainMessage(
                        PermissionControllerService::revokeRuntimePermissions,
                        PermissionControllerService.this, request, doDryRun, reason,
                        callerPackageName, callback));
            }

            @Override
            @Override
            public void getAppPermissions(String packageName, RemoteCallback callback) {
            public void getAppPermissions(String packageName, RemoteCallback callback) {
                checkNotNull(packageName, "packageName");
                checkNotNull(packageName, "packageName");
@@ -133,6 +190,27 @@ public abstract class PermissionControllerService extends Service {
        };
        };
    }
    }


    private void revokeRuntimePermissions(@NonNull Map<String, List<String>> requests,
            boolean doDryRun, @PermissionControllerManager.Reason int reason,
            @NonNull String callerPackageName, @NonNull RemoteCallback callback) {
        Map<String, List<String>> revoked = onRevokeRuntimePermissions(requests,
                doDryRun, reason, callerPackageName);

        checkNotNull(revoked);
        Bundle bundledizedRevoked = new Bundle();
        for (Map.Entry<String, List<String>> appRevocation : revoked.entrySet()) {
            checkNotNull(appRevocation.getKey());
            checkCollectionElementsNotNull(appRevocation.getValue(), "permissions");

            bundledizedRevoked.putStringArrayList(appRevocation.getKey(),
                    new ArrayList<>(appRevocation.getValue()));
        }

        Bundle result = new Bundle();
        result.putBundle(PermissionControllerManager.KEY_RESULT, bundledizedRevoked);
        callback.sendResult(result);
    }

    private void getAppPermissions(@NonNull String packageName, @NonNull RemoteCallback callback) {
    private void getAppPermissions(@NonNull String packageName, @NonNull RemoteCallback callback) {
        List<RuntimePermissionPresentationInfo> permissions = onGetAppPermissions(packageName);
        List<RuntimePermissionPresentationInfo> permissions = onGetAppPermissions(packageName);
        if (permissions != null && !permissions.isEmpty()) {
        if (permissions != null && !permissions.isEmpty()) {
Loading