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

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

Allow an app to drive permission backup+restore

In AOSP the permission backup+restore is driven by the system server,
but some OEMs might drive it from an app. Hence allow a privilidged app
to backup + restore permission backups.

Test: atest CtsBackupTestCases
Fixes: 141007569
Change-Id: Ic89b476948872c491de8ea54b83667afc0183bb4
parent d42515af
Loading
Loading
Loading
Loading
+8 −2
Original line number Diff line number Diff line
@@ -172,6 +172,7 @@ package android {
    field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
    field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE";
    field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
    field public static final String RESTORE_RUNTIME_PERMISSIONS = "android.permission.RESTORE_RUNTIME_PERMISSIONS";
    field public static final String RESTRICTED_VR_ACCESS = "android.permission.RESTRICTED_VR_ACCESS";
    field public static final String RETRIEVE_WINDOW_CONTENT = "android.permission.RETRIEVE_WINDOW_CONTENT";
    field public static final String REVIEW_ACCESSIBILITY_SERVICES = "android.permission.REVIEW_ACCESSIBILITY_SERVICES";
@@ -5695,7 +5696,10 @@ package android.os.telephony {
package android.permission {
  public final class PermissionControllerManager {
    method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
    method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<byte[]>);
    method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public void revokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull java.util.concurrent.Executor, @NonNull android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback);
    method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void stageAndApplyRuntimePermissionsBackup(@NonNull byte[], @NonNull android.os.UserHandle);
    field public static final int COUNT_ONLY_WHEN_GRANTED = 1; // 0x1
    field public static final int COUNT_WHEN_SYSTEM = 2; // 0x2
    field public static final int REASON_INSTALLER_POLICY_VIOLATION = 2; // 0x2
@@ -5709,17 +5713,19 @@ package android.permission {
  public abstract class PermissionControllerService extends android.app.Service {
    ctor public PermissionControllerService();
    method @BinderThread public void onApplyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>);
    method @NonNull public final android.os.IBinder onBind(android.content.Intent);
    method @BinderThread public abstract void onCountPermissionApps(@NonNull java.util.List<java.lang.String>, int, @NonNull java.util.function.IntConsumer);
    method @BinderThread public abstract void onGetAppPermissions(@NonNull String, @NonNull java.util.function.Consumer<java.util.List<android.permission.RuntimePermissionPresentationInfo>>);
    method @BinderThread public abstract void onGetPermissionUsages(boolean, long, @NonNull java.util.function.Consumer<java.util.List<android.permission.RuntimePermissionUsageInfo>>);
    method @BinderThread public abstract void onGetRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.OutputStream, @NonNull Runnable);
    method @BinderThread public abstract void onGrantOrUpgradeDefaultRuntimePermissions(@NonNull Runnable);
    method @BinderThread public abstract void onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>);
    method @BinderThread public abstract void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
    method @Deprecated @BinderThread public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>);
    method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
    method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable);
    method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>);
    method @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
    method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
    method @BinderThread public void onUpdateUserSensitive();
    field public static final String SERVICE_INTERFACE = "android.permission.PermissionControllerService";
  }
+3 −0
Original line number Diff line number Diff line
@@ -2235,8 +2235,11 @@ package android.os.strictmode {
package android.permission {

  public final class PermissionControllerManager {
    method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.RESTORE_RUNTIME_PERMISSIONS"}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
    method @RequiresPermission("android.permission.GET_RUNTIME_PERMISSIONS") public void getAppPermissions(@NonNull String, @NonNull android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, @Nullable android.os.Handler);
    method @RequiresPermission("android.permission.GET_RUNTIME_PERMISSIONS") public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<byte[]>);
    method @RequiresPermission("android.permission.REVOKE_RUNTIME_PERMISSIONS") public void revokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull java.util.concurrent.Executor, @NonNull android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback);
    method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.RESTORE_RUNTIME_PERMISSIONS"}) public void stageAndApplyRuntimePermissionsBackup(@NonNull byte[], @NonNull android.os.UserHandle);
    field public static final int COUNT_ONLY_WHEN_GRANTED = 1; // 0x1
    field public static final int COUNT_WHEN_SYSTEM = 2; // 0x2
    field public static final int REASON_INSTALLER_POLICY_VIOLATION = 2; // 0x2
+2 −2
Original line number Diff line number Diff line
@@ -31,8 +31,8 @@ oneway interface IPermissionController {
    void revokeRuntimePermissions(in Bundle request, boolean doDryRun, int reason,
            String callerPackageName, in AndroidFuture callback);
    void getRuntimePermissionBackup(in UserHandle user, in ParcelFileDescriptor pipe);
    void restoreRuntimePermissionBackup(in UserHandle user, in ParcelFileDescriptor pipe);
    void restoreDelayedRuntimePermissionBackup(String packageName, in UserHandle user,
    void stageAndApplyRuntimePermissionsBackup(in UserHandle user, in ParcelFileDescriptor pipe);
    void applyStagedRuntimePermissionBackup(String packageName, in UserHandle user,
            in AndroidFuture callback);
    void getAppPermissions(String packageName, in AndroidFuture callback);
    void revokeRuntimePermission(String packageName, String permissionName);
+71 −46
Original line number Diff line number Diff line
@@ -62,6 +62,7 @@ import libcore.util.EmptyArray;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -138,20 +139,6 @@ public final class PermissionControllerManager {
        public abstract void onRevokeRuntimePermissions(@NonNull Map<String, List<String>> revoked);
    }

    /**
     * Callback for delivering the result of {@link #getRuntimePermissionBackup}.
     *
     * @hide
     */
    public interface OnGetRuntimePermissionBackupCallback {
        /**
         * The result for {@link #getRuntimePermissionBackup}.
         *
         * @param backup The backup file
         */
        void onGetRuntimePermissionsBackup(@NonNull byte[] backup);
    }

    /**
     * Callback for delivering the result of {@link #getAppPermissions}.
     *
@@ -245,6 +232,24 @@ public final class PermissionControllerManager {
        mHandler = handler;
    }

    /**
     * Throw a {@link SecurityException} if not at least one of the permissions is granted.
     *
     * @param requiredPermissions A list of permissions. Any of of them if sufficient to pass the
     *                            check
     */
    private void enforceSomePermissionsGrantedToSelf(@NonNull String... requiredPermissions) {
        for (String requiredPermission : requiredPermissions) {
            if (mContext.checkSelfPermission(requiredPermission)
                    == PackageManager.PERMISSION_GRANTED) {
                return;
            }
        }

        throw new SecurityException("At lest one of the following permissions is required: "
                + Arrays.toString(requiredPermissions));
    }

    /**
     * Revoke a set of runtime permissions for various apps.
     *
@@ -268,11 +273,7 @@ public final class PermissionControllerManager {
        }

        // 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");
        }
        enforceSomePermissionsGrantedToSelf(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);

        mRemoteService.postAsync(service -> {
            Bundle bundledizedRequest = new Bundle();
@@ -358,46 +359,61 @@ public final class PermissionControllerManager {
     *
     * @param user The user to be backed up
     * @param executor Executor on which to invoke the callback
     * @param callback Callback to receive the result
     *
     * @hide
     * @param callback Callback to receive the result. The resulting backup-file is opaque and no
     *                 guarantees are made other than that the file can be send to
     *                 {@link #restoreRuntimePermissionBackup} in this and future versions of
     *                 Android.
     */
    @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
    public void getRuntimePermissionBackup(@NonNull UserHandle user,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull OnGetRuntimePermissionBackupCallback callback) {
            @NonNull Consumer<byte[]> callback) {
        checkNotNull(user);
        checkNotNull(executor);
        checkNotNull(callback);

        // Check required permission to fail immediately instead of inside the oneway binder call
        enforceSomePermissionsGrantedToSelf(Manifest.permission.GET_RUNTIME_PERMISSIONS);

        mRemoteService.postAsync(service -> RemoteStream.receiveBytes(remotePipe -> {
            service.getRuntimePermissionBackup(user, remotePipe);
        })).whenCompleteAsync((bytes, err) -> {
            if (err != null) {
                Log.e(TAG, "Error getting permission backup", err);
                callback.onGetRuntimePermissionsBackup(EmptyArray.BYTE);
                callback.accept(EmptyArray.BYTE);
            } else {
                callback.onGetRuntimePermissionsBackup(bytes);
                callback.accept(bytes);
            }
        }, executor);
    }

    /**
     * Restore a backup of the runtime permissions.
     * Restore a {@link #getRuntimePermissionBackup backup-file} of the runtime permissions.
     *
     * @param backup the backup to restore. The backup is sent asynchronously, hence it should not
     *               be modified after calling this method.
     * @param user The user to be restore
     * <p>This might leave some part of the backup-file unapplied if an package mentioned in the
     * backup-file is not yet installed. It is required that
     * {@link #applyStagedRuntimePermissionBackup} is called after any package is installed to
     * apply the rest of the backup-file.
     *
     * @hide
     * @param backup the backup-file to restore. The backup is sent asynchronously, hence it should
     *               not be modified after calling this method.
     * @param user The user to be restore
     */
    @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
    public void restoreRuntimePermissionBackup(@NonNull byte[] backup, @NonNull UserHandle user) {
    @RequiresPermission(anyOf = {
            Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
            Manifest.permission.RESTORE_RUNTIME_PERMISSIONS
    })
    public void stageAndApplyRuntimePermissionsBackup(@NonNull byte[] backup,
            @NonNull UserHandle user) {
        checkNotNull(backup);
        checkNotNull(user);

        // Check required permission to fail immediately instead of inside the oneway binder call
        enforceSomePermissionsGrantedToSelf(Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
                Manifest.permission.RESTORE_RUNTIME_PERMISSIONS);

        mRemoteService.postAsync(service -> RemoteStream.sendBytes(remotePipe -> {
            service.restoreRuntimePermissionBackup(user, remotePipe);
            service.stageAndApplyRuntimePermissionsBackup(user, remotePipe);
        }, backup))
                .whenComplete((nullResult, err) -> {
                    if (err != null) {
@@ -407,17 +423,22 @@ public final class PermissionControllerManager {
    }

    /**
     * Restore a backup of the runtime permissions that has been delayed.
     * Restore unapplied parts of a {@link #stageAndApplyRuntimePermissionsBackup previously staged}
     * backup-file of the runtime permissions.
     *
     * <p>This should be called every time after a package is installed until the callback
     * reports that there is no more unapplied backup left.
     *
     * @param packageName The package that is ready to have it's permissions restored.
     * @param user The user to restore
     * @param user The user the package belongs to
     * @param executor Executor to execute the callback on
     * @param callback Is called with {@code true} iff there is still more delayed backup left
     *
     * @hide
     * @param callback Is called with {@code true} iff there is still more unapplied backup left
     */
    @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
    public void restoreDelayedRuntimePermissionBackup(@NonNull String packageName,
    @RequiresPermission(anyOf = {
            Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
            Manifest.permission.RESTORE_RUNTIME_PERMISSIONS
    })
    public void applyStagedRuntimePermissionBackup(@NonNull String packageName,
            @NonNull UserHandle user,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull Consumer<Boolean> callback) {
@@ -426,13 +447,17 @@ public final class PermissionControllerManager {
        checkNotNull(executor);
        checkNotNull(callback);

        // Check required permission to fail immediately instead of inside the oneway binder call
        enforceSomePermissionsGrantedToSelf(Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
                Manifest.permission.RESTORE_RUNTIME_PERMISSIONS);

        mRemoteService.postAsync(service -> {
            AndroidFuture<Boolean> restoreDelayedRuntimePermissionBackupResult =
            AndroidFuture<Boolean> applyStagedRuntimePermissionBackupResult =
                    new AndroidFuture<>();
            service.restoreDelayedRuntimePermissionBackup(packageName, user,
                    restoreDelayedRuntimePermissionBackupResult);
            return restoreDelayedRuntimePermissionBackupResult;
        }).whenCompleteAsync((restoreDelayedRuntimePermissionBackupResult, err) -> {
            service.applyStagedRuntimePermissionBackup(packageName, user,
                    applyStagedRuntimePermissionBackupResult);
            return applyStagedRuntimePermissionBackupResult;
        }).whenCompleteAsync((applyStagedRuntimePermissionBackupResult, err) -> {
            long token = Binder.clearCallingIdentity();
            try {
                if (err != null) {
@@ -440,7 +465,7 @@ public final class PermissionControllerManager {
                    callback.accept(true);
                } else {
                    callback.accept(
                            Boolean.TRUE.equals(restoreDelayedRuntimePermissionBackupResult));
                            Boolean.TRUE.equals(applyStagedRuntimePermissionBackupResult));
                }
            } finally {
                Binder.restoreCallingIdentity(token);
+76 −24

File changed.

Preview size limit exceeded, changes collapsed.

Loading