From 7532c615cbd183af3309461dc938c3b69e996f06 Mon Sep 17 00:00:00 2001 From: "Philip P. Moltmann" Date: Sun, 20 Jan 2019 09:01:19 -0800 Subject: [PATCH] Plumming of perm restore via permission controller On Android backup restoration first restores and inital state. Then with the packages getting downloaded the leftover "delayed" backup gets restored bit by bit. This currently just created plumming mirroring the previously implemented behavior. Bug: 116738135 Test: Built Change-Id: I528b5f76dfca5bbdc9f69d6402b23b3d2ef11706 --- api/system-current.txt | 2 + .../permission/IPermissionController.aidl | 3 + .../PermissionControllerManager.java | 211 ++++++++++++++++++ .../PermissionControllerService.java | 54 +++++ 4 files changed, 270 insertions(+) diff --git a/api/system-current.txt b/api/system-current.txt index 8d98bc10efcfc..5eae36941dc3a 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5542,6 +5542,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");