diff --git a/api/system-current.txt b/api/system-current.txt index bf3c0a2716eba..f9403ffc3c791 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5546,6 +5546,8 @@ package android.permission { method @NonNull public abstract java.util.List onGetPermissionUsages(boolean, long); method public abstract void onGetRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.OutputStream); method public abstract boolean onIsApplicationQualifiedForRole(@NonNull String, @NonNull String); + method @BinderThread public abstract boolean onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle); + method @BinderThread public abstract void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream); method public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String); method @NonNull public abstract java.util.Map> onRevokeRuntimePermissions(@NonNull java.util.Map>, boolean, int, @NonNull String); field public static final String SERVICE_INTERFACE = "android.permission.PermissionControllerService"; diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl index ee6744b320f25..7e9ba5d4b6285 100644 --- a/core/java/android/permission/IPermissionController.aidl +++ b/core/java/android/permission/IPermissionController.aidl @@ -30,6 +30,9 @@ oneway interface IPermissionController { void revokeRuntimePermissions(in Bundle request, boolean doDryRun, int reason, String callerPackageName, in RemoteCallback callback); void getRuntimePermissionBackup(in UserHandle user, in ParcelFileDescriptor pipe); + void restoreRuntimePermissionBackup(in UserHandle user, in ParcelFileDescriptor pipe); + void restoreDelayedRuntimePermissionBackup(String packageName, in UserHandle user, + in RemoteCallback callback); void getAppPermissions(String packageName, in RemoteCallback callback); void revokeRuntimePermission(String packageName, String permissionName); void countPermissionApps(in List permissionNames, int flags, diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java index c59e5738165fc..89967c325dd27 100644 --- a/core/java/android/permission/PermissionControllerManager.java +++ b/core/java/android/permission/PermissionControllerManager.java @@ -24,6 +24,8 @@ import static com.android.internal.util.Preconditions.checkFlagsArgument; import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.internal.util.Preconditions.checkStringNotEmpty; +import static java.lang.Math.min; + import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.IntDef; @@ -60,6 +62,7 @@ import libcore.io.IoUtils; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -277,6 +280,49 @@ public final class PermissionControllerManager { user, executor, callback)); } + /** + * Restore a backup 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 + * + * @hide + */ + @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS) + public void restoreRuntimePermissionBackup(@NonNull byte[] backup, @NonNull UserHandle user) { + checkNotNull(backup); + checkNotNull(user); + + sRemoteService.scheduleAsyncRequest( + new PendingRestoreRuntimePermissionBackup(sRemoteService, backup, user)); + } + + /** + * Restore a backup of the runtime permissions that has been delayed. + * + * @param packageName The package that is ready to have it's permissions restored. + * @param user The user to restore + * @param executor Executor to execute the callback on + * @param callback Is called with {@code true} iff there is still more delayed backup left + * + * @hide + */ + @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS) + public void restoreDelayedRuntimePermissionBackup(@NonNull String packageName, + @NonNull UserHandle user, + @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer callback) { + checkNotNull(packageName); + checkNotNull(user); + checkNotNull(executor); + checkNotNull(callback); + + sRemoteService.scheduleRequest( + new PendingRestoreDelayedRuntimePermissionBackup(sRemoteService, packageName, + user, executor, callback)); + } + /** * Gets the runtime permissions for an app. * @@ -519,6 +565,80 @@ public final class PermissionControllerManager { } } + /** + * Task to send a large amount of data to a remote service. + */ + private static class FileWriterTask extends AsyncTask { + private static final int CHUNK_SIZE = 4 * 1024; + + private ParcelFileDescriptor mLocalPipe; + private ParcelFileDescriptor mRemotePipe; + + @Override + protected void onPreExecute() { + ParcelFileDescriptor[] pipe; + try { + pipe = ParcelFileDescriptor.createPipe(); + } catch (IOException e) { + Log.e(TAG, "Could not create pipe needed to send runtime permission backup", + e); + return; + } + + mRemotePipe = pipe[0]; + mLocalPipe = pipe[1]; + } + + /** + * Get the file descriptor the remote service should read the data from. + * + * @return The file the data should be read from + */ + ParcelFileDescriptor getRemotePipe() { + return mRemotePipe; + } + + /** + * Send the data to the remove service. + * + * @param in The data to send + * + * @return ignored + */ + @Override + protected Void doInBackground(byte[]... in) { + byte[] buffer = in[0]; + try (OutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream(mLocalPipe)) { + for (int offset = 0; offset < buffer.length; offset += CHUNK_SIZE) { + out.write(buffer, offset, min(CHUNK_SIZE, buffer.length - offset)); + } + } catch (IOException | NullPointerException e) { + Log.e(TAG, "Error sending runtime permission backup", e); + } + + return null; + } + + /** + * Interrupt the send of the data. + * + *

Needs to be called when canceling this task as it might be hung. + */ + void interruptRead() { + IoUtils.closeQuietly(mLocalPipe); + } + + @Override + protected void onCancelled() { + onPostExecute(null); + } + + @Override + protected void onPostExecute(Void ignored) { + IoUtils.closeQuietly(mLocalPipe); + } + } + /** * Request for {@link #revokeRuntimePermissions} */ @@ -667,6 +787,97 @@ public final class PermissionControllerManager { } } + /** + * Request for {@link #restoreRuntimePermissionBackup} + */ + private static final class PendingRestoreRuntimePermissionBackup implements + AbstractRemoteService.AsyncRequest { + private final @NonNull FileWriterTask mBackupSender; + private final @NonNull byte[] mBackup; + private final @NonNull UserHandle mUser; + + private PendingRestoreRuntimePermissionBackup(@NonNull RemoteService service, + @NonNull byte[] backup, @NonNull UserHandle user) { + mBackup = backup; + mUser = user; + + mBackupSender = new FileWriterTask(); + } + + @Override + public void run(@NonNull IPermissionController service) { + ParcelFileDescriptor remotePipe = mBackupSender.getRemotePipe(); + try { + service.restoreRuntimePermissionBackup(mUser, remotePipe); + } catch (RemoteException e) { + Log.e(TAG, "Error sending runtime permission backup", e); + mBackupSender.cancel(false); + } finally { + // Remote pipe end is duped by binder call. Local copy is not needed anymore + IoUtils.closeQuietly(remotePipe); + } + + mBackupSender.execute(mBackup); + } + } + + /** + * Request for {@link #restoreDelayedRuntimePermissionBackup(String, UserHandle, Executor, + * Consumer)} + */ + private static final class PendingRestoreDelayedRuntimePermissionBackup extends + AbstractRemoteService.PendingRequest { + private final @NonNull String mPackageName; + private final @NonNull UserHandle mUser; + private final @NonNull Executor mExecutor; + private final @NonNull Consumer mCallback; + + private final @NonNull RemoteCallback mRemoteCallback; + + private PendingRestoreDelayedRuntimePermissionBackup(@NonNull RemoteService service, + @NonNull String packageName, @NonNull UserHandle user, @NonNull Executor executor, + @NonNull Consumer callback) { + super(service); + + mPackageName = packageName; + mUser = user; + mExecutor = executor; + mCallback = callback; + + mRemoteCallback = new RemoteCallback(result -> executor.execute(() -> { + long token = Binder.clearCallingIdentity(); + try { + callback.accept(result.getBoolean(KEY_RESULT, false)); + } finally { + Binder.restoreCallingIdentity(token); + + finish(); + } + }), null); + } + + @Override + protected void onTimeout(RemoteService remoteService) { + long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute( + () -> mCallback.accept(true)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void run() { + try { + getService().getServiceInterface().restoreDelayedRuntimePermissionBackup( + mPackageName, mUser, mRemoteCallback); + } catch (RemoteException e) { + Log.e(TAG, "Error restoring delayed permissions for " + mPackageName, e); + } + } + } + /** * Request for {@link #getAppPermissions} */ diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java index 482f4a86419e7..fb6c061c536f5 100644 --- a/core/java/android/permission/PermissionControllerService.java +++ b/core/java/android/permission/PermissionControllerService.java @@ -28,6 +28,7 @@ import static com.android.internal.util.Preconditions.checkStringNotEmpty; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.Manifest; +import android.annotation.BinderThread; import android.annotation.NonNull; import android.annotation.SystemApi; import android.app.Service; @@ -48,6 +49,7 @@ import android.util.Log; import com.android.internal.util.Preconditions; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; @@ -103,6 +105,28 @@ public abstract class PermissionControllerService extends Service { public abstract void onGetRuntimePermissionsBackup(@NonNull UserHandle user, @NonNull OutputStream backup); + /** + * Restore a backup of the runtime permissions. + * + * @param user The user to restore + * @param backup The stream to read the backup from + */ + @BinderThread + public abstract void onRestoreRuntimePermissionsBackup(@NonNull UserHandle user, + @NonNull InputStream backup); + + /** + * Restore a delayed backup of the runtime permissions. + * + * @param packageName The app to restore + * @param user The user to restore + * + * @return {@code true} iff there is still delayed backup left + */ + @BinderThread + public abstract boolean onRestoreDelayedRuntimePermissionsBackup(@NonNull String packageName, + @NonNull UserHandle user); + /** * Gets the runtime permissions for an app. * @@ -206,6 +230,36 @@ public abstract class PermissionControllerService extends Service { PermissionControllerService.this, user, pipe)); } + @Override + public void restoreRuntimePermissionBackup(UserHandle user, ParcelFileDescriptor pipe) { + checkNotNull(user); + checkNotNull(pipe); + + enforceCallingPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS, null); + + try (InputStream backup = new ParcelFileDescriptor.AutoCloseInputStream(pipe)) { + onRestoreRuntimePermissionsBackup(user, backup); + } catch (IOException e) { + Log.e(LOG_TAG, "Could not open pipe to read backup from", e); + } + } + + @Override + public void restoreDelayedRuntimePermissionBackup(String packageName, UserHandle user, + RemoteCallback callback) { + checkNotNull(packageName); + checkNotNull(user); + checkNotNull(callback); + + enforceCallingPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS, null); + + boolean hasMoreBackup = onRestoreDelayedRuntimePermissionsBackup(packageName, user); + + Bundle result = new Bundle(); + result.putBoolean(PermissionControllerManager.KEY_RESULT, hasMoreBackup); + callback.sendResult(result); + } + @Override public void getAppPermissions(String packageName, RemoteCallback callback) { checkNotNull(packageName, "packageName");