Merge "Migrate PermissionControllerManager to ServiceConnector"
diff --git a/core/java/android/app/role/TEST_MAPPING b/core/java/android/app/role/TEST_MAPPING
new file mode 100644
index 0000000..11c2803
--- /dev/null
+++ b/core/java/android/app/role/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+    "presubmit": [
+        {
+            "name": "CtsRoleTestCases",
+            "options": [
+                {
+                    "include-filter": "android.app.role.cts.RoleManagerTest"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index 6e83e5a..de0bcb6 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -28,8 +28,6 @@
 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;
@@ -41,41 +39,35 @@
 import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.app.admin.DevicePolicyManager.PermissionGrantState;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.os.AsyncTask;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
 import android.os.RemoteCallback;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pair;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
-import com.android.internal.infra.AbstractRemoteService;
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.RemoteStream;
+import com.android.internal.infra.ServiceConnector;
+import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.Preconditions;
 
-import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
 
-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;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
@@ -90,14 +82,17 @@
 public final class PermissionControllerManager {
     private static final String TAG = PermissionControllerManager.class.getSimpleName();
 
+    private static final long UNBIND_TIMEOUT_MILLIS = 10000;
+    private static final int CHUNK_SIZE = 4 * 1024;
+
     private static final Object sLock = new Object();
 
     /**
      * Global remote services (per user) used by all {@link PermissionControllerManager managers}
      */
     @GuardedBy("sLock")
-    private static ArrayMap<Pair<Integer, Thread>, RemoteService> sRemoteServices
-            = new ArrayMap<>(1);
+    private static ArrayMap<Pair<Integer, Thread>, ServiceConnector<IPermissionController>>
+            sRemoteServices = new ArrayMap<>(1);
 
     /**
      * The key for retrieving the result from the returned bundle.
@@ -213,7 +208,8 @@
     }
 
     private final @NonNull Context mContext;
-    private final @NonNull RemoteService mRemoteService;
+    private final @NonNull ServiceConnector<IPermissionController> mRemoteService;
+    private final @NonNull Handler mHandler;
 
     /**
      * Create a new {@link PermissionControllerManager}.
@@ -227,15 +223,28 @@
         synchronized (sLock) {
             Pair<Integer, Thread> key = new Pair<>(context.getUserId(),
                     handler.getLooper().getThread());
-            RemoteService remoteService = sRemoteServices.get(key);
+            ServiceConnector<IPermissionController> remoteService = sRemoteServices.get(key);
             if (remoteService == null) {
                 Intent intent = new Intent(SERVICE_INTERFACE);
                 intent.setPackage(context.getPackageManager().getPermissionControllerPackageName());
                 ResolveInfo serviceInfo = context.getPackageManager().resolveService(intent, 0);
+                remoteService = new ServiceConnector.Impl<IPermissionController>(
+                        ActivityThread.currentApplication() /* context */,
+                        new Intent(SERVICE_INTERFACE)
+                                .setComponent(serviceInfo.getComponentInfo().getComponentName()),
+                        0 /* bindingFlags */, context.getUserId(),
+                        IPermissionController.Stub::asInterface) {
 
-                remoteService = new RemoteService(ActivityThread.currentApplication(),
-                        serviceInfo.getComponentInfo().getComponentName(), handler,
-                        context.getUser());
+                    @Override
+                    protected Handler getJobHandler() {
+                        return handler;
+                    }
+
+                    @Override
+                    protected long getAutoDisconnectTimeoutMs() {
+                        return UNBIND_TIMEOUT_MILLIS;
+                    }
+                };
                 sRemoteServices.put(key, remoteService);
             }
 
@@ -243,6 +252,7 @@
         }
 
         mContext = context;
+        mHandler = handler;
     }
 
     /**
@@ -274,8 +284,46 @@
                     + " required");
         }
 
-        mRemoteService.scheduleRequest(new PendingRevokeRuntimePermissionRequest(mRemoteService,
-                request, doDryRun, reason, mContext.getPackageName(), executor, callback));
+        mRemoteService.postAsync(service -> {
+            Bundle bundledizedRequest = new Bundle();
+            for (Map.Entry<String, List<String>> appRequest : request.entrySet()) {
+                bundledizedRequest.putStringArrayList(appRequest.getKey(),
+                        new ArrayList<>(appRequest.getValue()));
+            }
+
+            AndroidFuture<Bundle> revokeRuntimePermissionsResult = new AndroidFuture<>();
+            service.revokeRuntimePermissions(bundledizedRequest, doDryRun, reason,
+                    mContext.getPackageName(),
+                    new RemoteCallback(revokeRuntimePermissionsResult::complete));
+            return revokeRuntimePermissionsResult;
+        }).thenApply(revokeRuntimePermissionsResult -> {
+            Map<String, List<String>> revoked = new ArrayMap<>();
+            Bundle bundleizedRevoked = revokeRuntimePermissionsResult.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);
+            }
+            return revoked;
+        }).whenCompleteAsync((revoked, err) -> {
+            long token = Binder.clearCallingIdentity();
+            try {
+                if (err != null) {
+                    Log.e(TAG, "Failure when revoking runtime permissions", err);
+                    callback.onRevokeRuntimePermissions(Collections.emptyMap());
+                } else {
+                    callback.onRevokeRuntimePermissions(revoked);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }, executor);
     }
 
     /**
@@ -307,9 +355,28 @@
         checkNotNull(executor);
         checkNotNull(callback);
 
-        mRemoteService.scheduleRequest(new PendingSetRuntimePermissionGrantStateByDeviceAdmin(
-                mRemoteService, callerPackageName, packageName, permission, grantState, executor,
-                callback));
+        mRemoteService.postAsync(service -> {
+            CompletableFuture<Bundle> setRuntimePermissionGrantStateResult =
+                    new CompletableFuture<>();
+            service.setRuntimePermissionGrantStateByDeviceAdmin(
+                    callerPackageName, packageName, permission, grantState,
+                    new RemoteCallback(setRuntimePermissionGrantStateResult::complete));
+            return setRuntimePermissionGrantStateResult;
+        }).whenCompleteAsync((setRuntimePermissionGrantStateResult, err) -> {
+            long token = Binder.clearCallingIdentity();
+            try {
+                if (err != null) {
+                    Log.e(TAG, "Error setting permissions state for device admin " + packageName,
+                            err);
+                    callback.accept(false);
+                } else {
+                    callback.accept(
+                            setRuntimePermissionGrantStateResult.getBoolean(KEY_RESULT, false));
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }, executor);
     }
 
     /**
@@ -329,8 +396,16 @@
         checkNotNull(executor);
         checkNotNull(callback);
 
-        mRemoteService.scheduleRequest(new PendingGetRuntimePermissionBackup(mRemoteService,
-                user, executor, callback));
+        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);
+            } else {
+                callback.onGetRuntimePermissionsBackup(bytes);
+            }
+        }, executor);
     }
 
     /**
@@ -347,8 +422,14 @@
         checkNotNull(backup);
         checkNotNull(user);
 
-        mRemoteService.scheduleAsyncRequest(
-                new PendingRestoreRuntimePermissionBackup(mRemoteService, backup, user));
+        mRemoteService.postAsync(service -> RemoteStream.sendBytes(remotePipe -> {
+            service.restoreRuntimePermissionBackup(user, remotePipe);
+        }, backup))
+                .whenComplete((nullResult, err) -> {
+                    if (err != null) {
+                        Log.e(TAG, "Error sending permission backup", err);
+                    }
+                });
     }
 
     /**
@@ -371,9 +452,27 @@
         checkNotNull(executor);
         checkNotNull(callback);
 
-        mRemoteService.scheduleRequest(
-                new PendingRestoreDelayedRuntimePermissionBackup(mRemoteService, packageName,
-                        user, executor, callback));
+        mRemoteService.postAsync(service -> {
+            CompletableFuture<Bundle> restoreDelayedRuntimePermissionBackupResult =
+                    new CompletableFuture<>();
+            service.restoreDelayedRuntimePermissionBackup(packageName, user,
+                    new RemoteCallback(restoreDelayedRuntimePermissionBackupResult::complete));
+            return restoreDelayedRuntimePermissionBackupResult;
+        }).whenCompleteAsync((restoreDelayedRuntimePermissionBackupResult, err) -> {
+            long token = Binder.clearCallingIdentity();
+            try {
+                if (err != null) {
+                    Log.e(TAG, "Error restoring delayed permissions for " + packageName, err);
+                    callback.accept(true);
+                } else {
+                    callback.accept(
+                            restoreDelayedRuntimePermissionBackupResult
+                                    .getBoolean(KEY_RESULT, false));
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }, executor);
     }
 
     /**
@@ -390,9 +489,25 @@
             @NonNull OnGetAppPermissionResultCallback callback, @Nullable Handler handler) {
         checkNotNull(packageName);
         checkNotNull(callback);
+        Handler finalHandler = handler != null ? handler : mHandler;
 
-        mRemoteService.scheduleRequest(new PendingGetAppPermissionRequest(mRemoteService,
-                packageName, callback, handler == null ? mRemoteService.getHandler() : handler));
+        mRemoteService.postAsync(service -> {
+            CompletableFuture<Bundle> getAppPermissionsResult = new CompletableFuture<>();
+            service.getAppPermissions(packageName,
+                    new RemoteCallback(getAppPermissionsResult::complete));
+            return getAppPermissionsResult;
+        }).whenComplete((getAppPermissionsResult, err) -> finalHandler.post(() -> {
+            if (err != null) {
+                Log.e(TAG, "Error getting app permission", err);
+                callback.onGetAppPermissions(Collections.emptyList());
+            } else {
+                List<RuntimePermissionPresentationInfo> permissions = null;
+                if (getAppPermissionsResult != null) {
+                    permissions = getAppPermissionsResult.getParcelableArrayList(KEY_RESULT);
+                }
+                callback.onGetAppPermissions(CollectionUtils.emptyIfNull(permissions));
+            }
+        }));
     }
 
     /**
@@ -409,8 +524,7 @@
         checkNotNull(packageName);
         checkNotNull(permissionName);
 
-        mRemoteService.scheduleAsyncRequest(new PendingRevokeAppPermissionRequest(packageName,
-                permissionName));
+        mRemoteService.run(service -> service.revokeRuntimePermission(packageName, permissionName));
     }
 
     /**
@@ -431,10 +545,23 @@
         checkCollectionElementsNotNull(permissionNames, "permissionNames");
         checkFlagsArgument(flags, COUNT_WHEN_SYSTEM | COUNT_ONLY_WHEN_GRANTED);
         checkNotNull(callback);
+        Handler finalHandler = handler != null ? handler : mHandler;
 
-        mRemoteService.scheduleRequest(new PendingCountPermissionAppsRequest(mRemoteService,
-                permissionNames, flags, callback,
-                handler == null ? mRemoteService.getHandler() : handler));
+        mRemoteService.postAsync(service -> {
+            CompletableFuture<Bundle> countPermissionAppsResult = new CompletableFuture<>();
+            service.countPermissionApps(permissionNames, flags,
+                    new RemoteCallback(countPermissionAppsResult::complete));
+            return countPermissionAppsResult;
+        }).whenComplete((countPermissionAppsResult, err) -> finalHandler.post(() -> {
+            if (err != null) {
+                Log.e(TAG, "Error counting permission apps", err);
+                callback.onCountPermissionApps(0);
+            } else {
+                callback.onCountPermissionApps(countPermissionAppsResult != null
+                        ? countPermissionAppsResult.getInt(KEY_RESULT)
+                        : 0);
+            }
+        }));
     }
 
     /**
@@ -455,8 +582,27 @@
         checkNotNull(executor);
         checkNotNull(callback);
 
-        mRemoteService.scheduleRequest(new PendingGetPermissionUsagesRequest(mRemoteService,
-                countSystem, numMillis, executor, callback));
+
+        mRemoteService.postAsync(service -> {
+            CompletableFuture<Bundle> getPermissionUsagesResult = new CompletableFuture<>();
+            service.getPermissionUsages(countSystem, numMillis,
+                    new RemoteCallback(getPermissionUsagesResult::complete));
+            return getPermissionUsagesResult;
+        }).whenCompleteAsync((getPermissionUsagesResult, err) -> {
+            if (err != null) {
+                Log.e(TAG, "Error getting permission usages", err);
+                callback.onPermissionUsageResult(Collections.emptyList());
+            } else {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    callback.onPermissionUsageResult(getPermissionUsagesResult != null
+                            ? getPermissionUsagesResult.getParcelableArrayList(KEY_RESULT)
+                            : Collections.emptyList());
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        }, executor);
     }
 
     /**
@@ -472,749 +618,19 @@
     @RequiresPermission(Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY)
     public void grantOrUpgradeDefaultRuntimePermissions(
             @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
-        mRemoteService.scheduleRequest(new PendingGrantOrUpgradeDefaultRuntimePermissionsRequest(
-                mRemoteService, executor, callback));
-    }
-
-    /**
-     * A connection to the remote service
-     */
-    static final class RemoteService extends
-            AbstractMultiplePendingRequestsRemoteService<RemoteService, IPermissionController> {
-        private static final long UNBIND_TIMEOUT_MILLIS = 10000;
-        private static final long MESSAGE_TIMEOUT_MILLIS = 30000;
-
-        /**
-         * Create a connection to the remote service
-         *
-         * @param context A context to use
-         * @param componentName The component of the service to connect to
-         * @param user User the remote service should be connected as
-         */
-        RemoteService(@NonNull Context context, @NonNull ComponentName componentName,
-                @NonNull Handler handler, @NonNull UserHandle user) {
-            super(context, SERVICE_INTERFACE, componentName, user.getIdentifier(),
-                    service -> Log.e(TAG, "RemoteService " + service + " died"),
-                    handler, 0, false, 1);
-        }
-
-        /**
-         * @return The default handler used by this service.
-         */
-        Handler getHandler() {
-            return mHandler;
-        }
-
-        @Override
-        protected @NonNull IPermissionController getServiceInterface(@NonNull IBinder binder) {
-            return IPermissionController.Stub.asInterface(binder);
-        }
-
-        @Override
-        protected long getTimeoutIdleBindMillis() {
-            return UNBIND_TIMEOUT_MILLIS;
-        }
-
-        @Override
-        protected long getRemoteRequestMillis() {
-            return MESSAGE_TIMEOUT_MILLIS;
-        }
-
-        @Override
-        public void scheduleRequest(@NonNull BasePendingRequest<RemoteService,
-                IPermissionController> pendingRequest) {
-            super.scheduleRequest(pendingRequest);
-        }
-
-        @Override
-        public void scheduleAsyncRequest(@NonNull AsyncRequest<IPermissionController> request) {
-            super.scheduleAsyncRequest(request);
-        }
-    }
-
-    /**
-     * Task to read a large amount of data from a remote service.
-     */
-    private static class FileReaderTask<Callback extends Consumer<byte[]>>
-            extends AsyncTask<Void, Void, byte[]> {
-        private ParcelFileDescriptor mLocalPipe;
-        private ParcelFileDescriptor mRemotePipe;
-
-        private final @NonNull Callback mCallback;
-
-        FileReaderTask(@NonNull Callback callback) {
-            mCallback = callback;
-        }
-
-        @Override
-        protected void onPreExecute() {
-            ParcelFileDescriptor[] pipe;
-            try {
-                pipe = ParcelFileDescriptor.createPipe();
-            } catch (IOException e) {
-                Log.e(TAG, "Could not create pipe needed to get runtime permission backup", e);
-                return;
+        mRemoteService.postAsync(service -> {
+            CompletableFuture<Bundle> grantOrUpgradeDefaultRuntimePermissionsResult =
+                    new CompletableFuture<>();
+            service.grantOrUpgradeDefaultRuntimePermissions(
+                    new RemoteCallback(grantOrUpgradeDefaultRuntimePermissionsResult::complete));
+            return grantOrUpgradeDefaultRuntimePermissionsResult;
+        }).whenCompleteAsync((grantOrUpgradeDefaultRuntimePermissionsResult, err) -> {
+            if (err != null) {
+                Log.e(TAG, "Error granting or upgrading runtime permissions", err);
+                callback.accept(false);
+            } else {
+                callback.accept(grantOrUpgradeDefaultRuntimePermissionsResult != null);
             }
-
-            mLocalPipe = pipe[0];
-            mRemotePipe = pipe[1];
-        }
-
-        /**
-         * Get the file descriptor the remote service should write the data to.
-         *
-         * <p>Needs to be closed <u>locally</u> before the FileReader can finish.
-         *
-         * @return The file the data should be written to
-         */
-        ParcelFileDescriptor getRemotePipe() {
-            return mRemotePipe;
-        }
-
-        @Override
-        protected byte[] doInBackground(Void... ignored) {
-            ByteArrayOutputStream combinedBuffer = new ByteArrayOutputStream();
-
-            try (InputStream in = new ParcelFileDescriptor.AutoCloseInputStream(mLocalPipe)) {
-                byte[] buffer = new byte[16 * 1024];
-
-                while (!isCancelled()) {
-                    int numRead = in.read(buffer);
-                    if (numRead == -1) {
-                        break;
-                    }
-
-                    combinedBuffer.write(buffer, 0, numRead);
-                }
-            } catch (IOException | NullPointerException e) {
-                Log.e(TAG, "Error reading runtime permission backup", e);
-                combinedBuffer.reset();
-            }
-
-            return combinedBuffer.toByteArray();
-        }
-
-        /**
-         * Interrupt the reading of the data.
-         *
-         * <p>Needs to be called when canceling this task as it might be hung.
-         */
-        void interruptRead() {
-            IoUtils.closeQuietly(mLocalPipe);
-        }
-
-        @Override
-        protected void onCancelled() {
-            onPostExecute(new byte[]{});
-        }
-
-        @Override
-        protected void onPostExecute(byte[] backup) {
-            IoUtils.closeQuietly(mLocalPipe);
-            mCallback.accept(backup);
-        }
-    }
-
-    /**
-     * Task to send a large amount of data to a remote service.
-     */
-    private static class FileWriterTask extends AsyncTask<byte[], Void, Void> {
-        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.
-         *
-         * <p>Needs to be called when canceling this task as it might be hung.
-         */
-        void interruptWrite() {
-            IoUtils.closeQuietly(mLocalPipe);
-        }
-
-        @Override
-        protected void onCancelled() {
-            onPostExecute(null);
-        }
-
-        @Override
-        protected void onPostExecute(Void ignored) {
-            IoUtils.closeQuietly(mLocalPipe);
-        }
-    }
-
-    /**
-     * 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 Executor mExecutor;
-        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;
-            mExecutor = executor;
-            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) {
-            long token = Binder.clearCallingIdentity();
-            try {
-                mExecutor.execute(
-                        () -> mCallback.onRevokeRuntimePermissions(Collections.emptyMap()));
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @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 #getRuntimePermissionBackup}
-     */
-    private static final class PendingGetRuntimePermissionBackup extends
-            AbstractRemoteService.PendingRequest<RemoteService, IPermissionController>
-            implements Consumer<byte[]> {
-        private final @NonNull FileReaderTask<PendingGetRuntimePermissionBackup> mBackupReader;
-        private final @NonNull Executor mExecutor;
-        private final @NonNull OnGetRuntimePermissionBackupCallback mCallback;
-        private final @NonNull UserHandle mUser;
-
-        private PendingGetRuntimePermissionBackup(@NonNull RemoteService service,
-                @NonNull UserHandle user, @NonNull @CallbackExecutor Executor executor,
-                @NonNull OnGetRuntimePermissionBackupCallback callback) {
-            super(service);
-
-            mUser = user;
-            mExecutor = executor;
-            mCallback = callback;
-
-            mBackupReader = new FileReaderTask<>(this);
-        }
-
-        @Override
-        protected void onTimeout(RemoteService remoteService) {
-            mBackupReader.cancel(true);
-            mBackupReader.interruptRead();
-        }
-
-        @Override
-        public void run() {
-            mBackupReader.execute();
-
-            ParcelFileDescriptor remotePipe = mBackupReader.getRemotePipe();
-            try {
-                getService().getServiceInterface().getRuntimePermissionBackup(mUser, remotePipe);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error getting runtime permission backup", e);
-            } finally {
-                // Remote pipe end is duped by binder call. Local copy is not needed anymore
-                IoUtils.closeQuietly(remotePipe);
-            }
-        }
-
-        /**
-         * Called when the {@link #mBackupReader} finished reading the file.
-         *
-         * @param backup The data read
-         */
-        @Override
-        public void accept(byte[] backup) {
-            long token = Binder.clearCallingIdentity();
-            try {
-                mExecutor.execute(() -> mCallback.onGetRuntimePermissionsBackup(backup));
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-
-            finish();
-        }
-    }
-
-    /**
-     * Request for {@link #getRuntimePermissionBackup}
-     */
-    private static final class PendingSetRuntimePermissionGrantStateByDeviceAdmin extends
-            AbstractRemoteService.PendingRequest<RemoteService, IPermissionController> {
-        private final @NonNull String mCallerPackageName;
-        private final @NonNull String mPackageName;
-        private final @NonNull String mPermission;
-        private final @PermissionGrantState int mGrantState;
-
-        private final @NonNull Executor mExecutor;
-        private final @NonNull Consumer<Boolean> mCallback;
-        private final @NonNull RemoteCallback mRemoteCallback;
-
-        private PendingSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull RemoteService service,
-                @NonNull String callerPackageName, @NonNull String packageName,
-                @NonNull String permission, @PermissionGrantState int grantState,
-                @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
-            super(service);
-
-            mCallerPackageName = callerPackageName;
-            mPackageName = packageName;
-            mPermission = permission;
-            mGrantState = grantState;
-            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(false));
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
-        public void run() {
-            try {
-                getService().getServiceInterface().setRuntimePermissionGrantStateByDeviceAdmin(
-                        mCallerPackageName, mPackageName, mPermission, mGrantState, mRemoteCallback);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error setting permissions state for device admin " + mPackageName,
-                        e);
-            }
-        }
-    }
-
-    /**
-     * Request for {@link #restoreRuntimePermissionBackup}
-     */
-    private static final class PendingRestoreRuntimePermissionBackup implements
-            AbstractRemoteService.AsyncRequest<IPermissionController> {
-        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) {
-            mBackupSender.execute(mBackup);
-
-            ParcelFileDescriptor remotePipe = mBackupSender.getRemotePipe();
-            try {
-                service.restoreRuntimePermissionBackup(mUser, remotePipe);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error sending runtime permission backup", e);
-                mBackupSender.cancel(false);
-                mBackupSender.interruptWrite();
-            } finally {
-                // Remote pipe end is duped by binder call. Local copy is not needed anymore
-                IoUtils.closeQuietly(remotePipe);
-            }
-        }
-    }
-
-    /**
-     * Request for {@link #restoreDelayedRuntimePermissionBackup(String, UserHandle, Executor,
-     * Consumer<Boolean>)}
-     */
-    private static final class PendingRestoreDelayedRuntimePermissionBackup extends
-            AbstractRemoteService.PendingRequest<RemoteService, IPermissionController> {
-        private final @NonNull String mPackageName;
-        private final @NonNull UserHandle mUser;
-        private final @NonNull Executor mExecutor;
-        private final @NonNull Consumer<Boolean> mCallback;
-
-        private final @NonNull RemoteCallback mRemoteCallback;
-
-        private PendingRestoreDelayedRuntimePermissionBackup(@NonNull RemoteService service,
-                @NonNull String packageName, @NonNull UserHandle user, @NonNull Executor executor,
-                @NonNull Consumer<Boolean> 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}
-     */
-    private static final class PendingGetAppPermissionRequest extends
-            AbstractRemoteService.PendingRequest<RemoteService, IPermissionController> {
-        private final @NonNull String mPackageName;
-        private final @NonNull OnGetAppPermissionResultCallback mCallback;
-
-        private final @NonNull RemoteCallback mRemoteCallback;
-
-        private PendingGetAppPermissionRequest(@NonNull RemoteService service,
-                @NonNull String packageName, @NonNull OnGetAppPermissionResultCallback callback,
-                @NonNull Handler handler) {
-            super(service);
-
-            mPackageName = packageName;
-            mCallback = callback;
-
-            mRemoteCallback = new RemoteCallback(result -> {
-                final List<RuntimePermissionPresentationInfo> reportedPermissions;
-                List<RuntimePermissionPresentationInfo> permissions = null;
-                if (result != null) {
-                    permissions = result.getParcelableArrayList(KEY_RESULT);
-                }
-                if (permissions == null) {
-                    permissions = Collections.emptyList();
-                }
-                reportedPermissions = permissions;
-
-                callback.onGetAppPermissions(reportedPermissions);
-
-                finish();
-            }, handler);
-        }
-
-        @Override
-        protected void onTimeout(RemoteService remoteService) {
-            mCallback.onGetAppPermissions(Collections.emptyList());
-        }
-
-        @Override
-        public void run() {
-            try {
-                getService().getServiceInterface().getAppPermissions(mPackageName, mRemoteCallback);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error getting app permission", e);
-            }
-        }
-    }
-
-    /**
-     * Request for {@link #revokeRuntimePermission}
-     */
-    private static final class PendingRevokeAppPermissionRequest
-            implements AbstractRemoteService.AsyncRequest<IPermissionController> {
-        private final @NonNull String mPackageName;
-        private final @NonNull String mPermissionName;
-
-        private PendingRevokeAppPermissionRequest(@NonNull String packageName,
-                @NonNull String permissionName) {
-            mPackageName = packageName;
-            mPermissionName = permissionName;
-        }
-
-        @Override
-        public void run(IPermissionController remoteInterface) {
-            try {
-                remoteInterface.revokeRuntimePermission(mPackageName, mPermissionName);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error revoking app permission", e);
-            }
-        }
-    }
-
-    /**
-     * Request for {@link #countPermissionApps}
-     */
-    private static final class PendingCountPermissionAppsRequest extends
-            AbstractRemoteService.PendingRequest<RemoteService, IPermissionController> {
-        private final @NonNull List<String> mPermissionNames;
-        private final @NonNull OnCountPermissionAppsResultCallback mCallback;
-        private final @CountPermissionAppsFlag int mFlags;
-
-        private final @NonNull RemoteCallback mRemoteCallback;
-
-        private PendingCountPermissionAppsRequest(@NonNull RemoteService service,
-                @NonNull List<String> permissionNames, @CountPermissionAppsFlag int flags,
-                @NonNull OnCountPermissionAppsResultCallback callback, @NonNull Handler handler) {
-            super(service);
-
-            mPermissionNames = permissionNames;
-            mFlags = flags;
-            mCallback = callback;
-
-            mRemoteCallback = new RemoteCallback(result -> {
-                final int numApps;
-                if (result != null) {
-                    numApps = result.getInt(KEY_RESULT);
-                } else {
-                    numApps = 0;
-                }
-
-                callback.onCountPermissionApps(numApps);
-
-                finish();
-            }, handler);
-        }
-
-        @Override
-        protected void onTimeout(RemoteService remoteService) {
-            mCallback.onCountPermissionApps(0);
-        }
-
-        @Override
-        public void run() {
-            try {
-                getService().getServiceInterface().countPermissionApps(mPermissionNames,
-                        mFlags, mRemoteCallback);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error counting permission apps", e);
-            }
-        }
-    }
-
-    /**
-     * Request for {@link #getPermissionUsages}
-     */
-    private static final class PendingGetPermissionUsagesRequest extends
-            AbstractRemoteService.PendingRequest<RemoteService, IPermissionController> {
-        private final @NonNull OnPermissionUsageResultCallback mCallback;
-        private final boolean mCountSystem;
-        private final long mNumMillis;
-
-        private final @NonNull RemoteCallback mRemoteCallback;
-
-        private PendingGetPermissionUsagesRequest(@NonNull RemoteService service,
-                boolean countSystem, long numMillis, @NonNull @CallbackExecutor Executor executor,
-                @NonNull OnPermissionUsageResultCallback callback) {
-            super(service);
-
-            mCountSystem = countSystem;
-            mNumMillis = numMillis;
-            mCallback = callback;
-
-            mRemoteCallback = new RemoteCallback(result -> executor.execute(() -> {
-                long token = Binder.clearCallingIdentity();
-                try {
-                    final List<RuntimePermissionUsageInfo> reportedUsers;
-                    List<RuntimePermissionUsageInfo> users = null;
-                    if (result != null) {
-                        users = result.getParcelableArrayList(KEY_RESULT);
-                    } else {
-                        users = Collections.emptyList();
-                    }
-                    reportedUsers = users;
-
-                    callback.onPermissionUsageResult(reportedUsers);
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-
-                    finish();
-                }
-            }), null);
-        }
-
-        @Override
-        protected void onTimeout(RemoteService remoteService) {
-            mCallback.onPermissionUsageResult(Collections.emptyList());
-        }
-
-        @Override
-        public void run() {
-            try {
-                getService().getServiceInterface().getPermissionUsages(mCountSystem, mNumMillis,
-                        mRemoteCallback);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error counting permission users", e);
-            }
-        }
-    }
-
-    /**
-     * Request for {@link #grantOrUpgradeDefaultRuntimePermissions(Executor, Consumer)}
-     */
-    private static final class PendingGrantOrUpgradeDefaultRuntimePermissionsRequest extends
-            AbstractRemoteService.PendingRequest<RemoteService, IPermissionController> {
-        private final @NonNull Consumer<Boolean> mCallback;
-
-        private final @NonNull RemoteCallback mRemoteCallback;
-
-        private PendingGrantOrUpgradeDefaultRuntimePermissionsRequest(
-                @NonNull RemoteService service,  @NonNull @CallbackExecutor Executor executor,
-                @NonNull Consumer<Boolean> callback) {
-            super(service);
-            mCallback = callback;
-
-            mRemoteCallback = new RemoteCallback(result -> executor.execute(() -> {
-                long token = Binder.clearCallingIdentity();
-                try {
-                    callback.accept(result != null);
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-                    finish();
-                }
-            }), null);
-        }
-
-        @Override
-        protected void onTimeout(RemoteService remoteService) {
-            long token = Binder.clearCallingIdentity();
-            try {
-                mCallback.accept(false);
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
-        public void run() {
-            try {
-                getService().getServiceInterface().grantOrUpgradeDefaultRuntimePermissions(
-                        mRemoteCallback);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error granting or upgrading runtime permissions", e);
-            }
-        }
+        }, executor);
     }
 }
diff --git a/core/java/com/android/internal/infra/AndroidFuture.java b/core/java/com/android/internal/infra/AndroidFuture.java
index a459de4..c9e2d5f 100644
--- a/core/java/com/android/internal/infra/AndroidFuture.java
+++ b/core/java/com/android/internal/infra/AndroidFuture.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.infra;
 
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+
+import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Handler;
@@ -30,10 +33,13 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
 import java.util.function.Function;
+import java.util.function.Supplier;
 
 /**
  * A customized {@link CompletableFuture} with focus on reducing the number of allocations involved
@@ -42,9 +48,12 @@
  * In particular this involves allocations optimizations in:
  * <ul>
  *     <li>{@link #thenCompose(Function)}</li>
+ *     <li>{@link #thenApply(Function)}</li>
+ *     <li>{@link #thenCombine(CompletionStage, BiFunction)}</li>
  *     <li>{@link #orTimeout(long, TimeUnit)}</li>
  *     <li>{@link #whenComplete(BiConsumer)}</li>
  * </ul>
+ * As well as their *Async versions.
  *
  * @param <T> see {@link CompletableFuture}
  */
@@ -52,8 +61,11 @@
 
     private static final String LOG_TAG = AndroidFuture.class.getSimpleName();
 
-    @GuardedBy("this")
+    private final @NonNull Object mLock = new Object();
+    @GuardedBy("mLock")
     private @Nullable BiConsumer<? super T, ? super Throwable> mListener;
+    @GuardedBy("mLock")
+    private @Nullable Executor mListenerExecutor = DIRECT_EXECUTOR;
     private @NonNull Handler mTimeoutHandler = Handler.getMain();
 
     @Override
@@ -74,27 +86,44 @@
         return super.completeExceptionally(ex);
     }
 
-    private void onCompleted(@Nullable T res, @Nullable Throwable err) {
+    @CallSuper
+    protected void onCompleted(@Nullable T res, @Nullable Throwable err) {
         cancelTimeout();
 
         BiConsumer<? super T, ? super Throwable> listener;
-        synchronized (this) {
+        synchronized (mLock) {
             listener = mListener;
             mListener = null;
         }
 
         if (listener != null) {
-            callListener(listener, res, err);
+            callListenerAsync(listener, res, err);
         }
     }
 
     @Override
-    public AndroidFuture<T> whenComplete(
-            @NonNull BiConsumer<? super T, ? super Throwable> action) {
+    public AndroidFuture<T> whenComplete(@NonNull BiConsumer<? super T, ? super Throwable> action) {
+        return whenCompleteAsync(action, DIRECT_EXECUTOR);
+    }
+
+    @Override
+    public AndroidFuture<T> whenCompleteAsync(
+            @NonNull BiConsumer<? super T, ? super Throwable> action,
+            @NonNull Executor executor) {
         Preconditions.checkNotNull(action);
-        synchronized (this) {
+        Preconditions.checkNotNull(executor);
+        synchronized (mLock) {
             if (!isDone()) {
                 BiConsumer<? super T, ? super Throwable> oldListener = mListener;
+
+                if (oldListener != null && executor != mListenerExecutor) {
+                    // 2 listeners with different executors
+                    // Too complex - give up on saving allocations and delegate to superclass
+                    super.whenCompleteAsync(action, executor);
+                    return this;
+                }
+
+                mListenerExecutor = executor;
                 mListener = oldListener == null
                         ? action
                         : (res, err) -> {
@@ -115,10 +144,21 @@
         } catch (Throwable e) {
             err = e;
         }
-        callListener(action, res, err);
+        callListenerAsync(action, res, err);
         return this;
     }
 
+    private void callListenerAsync(BiConsumer<? super T, ? super Throwable> listener,
+            @Nullable T res, @Nullable Throwable err) {
+        if (mListenerExecutor == DIRECT_EXECUTOR) {
+            callListener(listener, res, err);
+        } else {
+            mListenerExecutor.execute(PooledLambda
+                    .obtainRunnable(AndroidFuture::callListener, listener, res, err)
+                    .recycleOnUse());
+        }
+    }
+
     /**
      * Calls the provided listener, handling any exceptions that may arise.
      */
@@ -137,8 +177,7 @@
                 } else {
                     // listener exception-case threw
                     // give up on listener but preserve the original exception when throwing up
-                    ExceptionUtils.getRootCause(t).initCause(err);
-                    throw t;
+                    throw ExceptionUtils.appendCause(t, err);
                 }
             }
         } catch (Throwable t2) {
@@ -163,8 +202,14 @@
         }
     }
 
-    protected void cancelTimeout() {
+    /**
+     * Cancel all timeouts previously set with {@link #orTimeout}, if any.
+     *
+     * @return {@code this} for chaining
+     */
+    public AndroidFuture<T> cancelTimeout() {
         mTimeoutHandler.removeCallbacksAndMessages(this);
+        return this;
     }
 
     /**
@@ -179,48 +224,193 @@
     @Override
     public <U> AndroidFuture<U> thenCompose(
             @NonNull Function<? super T, ? extends CompletionStage<U>> fn) {
-        return (AndroidFuture<U>) new ThenCompose<>(this, fn);
+        return thenComposeAsync(fn, DIRECT_EXECUTOR);
     }
 
-    private static class ThenCompose<T, U> extends AndroidFuture<Object>
-            implements BiConsumer<Object, Throwable> {
-        private final AndroidFuture<T> mSource;
-        private Function<? super T, ? extends CompletionStage<U>> mFn;
+    @Override
+    public <U> AndroidFuture<U> thenComposeAsync(
+            @NonNull Function<? super T, ? extends CompletionStage<U>> fn,
+            @NonNull Executor executor) {
+        return new ThenComposeAsync<>(this, fn, executor);
+    }
 
-        ThenCompose(@NonNull AndroidFuture<T> source,
-                @NonNull Function<? super T, ? extends CompletionStage<U>> fn) {
-            mSource = source;
+    private static class ThenComposeAsync<T, U> extends AndroidFuture<U>
+            implements BiConsumer<Object, Throwable>, Runnable {
+        private volatile T mSourceResult = null;
+        private final Executor mExecutor;
+        private volatile Function<? super T, ? extends CompletionStage<U>> mFn;
+
+        ThenComposeAsync(@NonNull AndroidFuture<T> source,
+                @NonNull Function<? super T, ? extends CompletionStage<U>> fn,
+                @NonNull Executor executor) {
             mFn = Preconditions.checkNotNull(fn);
+            mExecutor = Preconditions.checkNotNull(executor);
+
             // subscribe to first job completion
             source.whenComplete(this);
         }
 
         @Override
         public void accept(Object res, Throwable err) {
-            Function<? super T, ? extends CompletionStage<U>> fn;
-            synchronized (this) {
-                fn = mFn;
-                mFn = null;
-            }
-            if (fn != null) {
+            if (err != null) {
+                // first or second job failed
+                completeExceptionally(err);
+            } else if (mFn != null) {
                 // first job completed
-                CompletionStage<U> secondJob;
-                try {
-                    secondJob = Preconditions.checkNotNull(fn.apply((T) res));
-                } catch (Throwable t) {
-                    completeExceptionally(t);
-                    return;
-                }
-                // subscribe to second job completion
-                secondJob.whenComplete(this);
+                mSourceResult = (T) res;
+                // subscribe to second job completion asynchronously
+                mExecutor.execute(this);
             } else {
                 // second job completed
-                if (err != null) {
-                    completeExceptionally(err);
-                } else {
-                    complete(res);
+                complete((U) res);
+            }
+        }
+
+        @Override
+        public void run() {
+            CompletionStage<U> secondJob;
+            try {
+                secondJob = Preconditions.checkNotNull(mFn.apply(mSourceResult));
+            } catch (Throwable t) {
+                completeExceptionally(t);
+                return;
+            } finally {
+                // Marks first job complete
+                mFn = null;
+            }
+            // subscribe to second job completion
+            secondJob.whenComplete(this);
+        }
+    }
+
+    @Override
+    public <U> AndroidFuture<U> thenApply(@NonNull Function<? super T, ? extends U> fn) {
+        return thenApplyAsync(fn, DIRECT_EXECUTOR);
+    }
+
+    @Override
+    public <U> AndroidFuture<U> thenApplyAsync(@NonNull Function<? super T, ? extends U> fn,
+            @NonNull Executor executor) {
+        return new ThenApplyAsync<>(this, fn, executor);
+    }
+
+    private static class ThenApplyAsync<T, U> extends AndroidFuture<U>
+            implements BiConsumer<T, Throwable>, Runnable {
+        private volatile T mSourceResult = null;
+        private final Executor mExecutor;
+        private final Function<? super T, ? extends U> mFn;
+
+        ThenApplyAsync(@NonNull AndroidFuture<T> source,
+                @NonNull Function<? super T, ? extends U> fn,
+                @NonNull Executor executor) {
+            mExecutor = Preconditions.checkNotNull(executor);
+            mFn = Preconditions.checkNotNull(fn);
+
+            // subscribe to job completion
+            source.whenComplete(this);
+        }
+
+        @Override
+        public void accept(T res, Throwable err) {
+            if (err != null) {
+                completeExceptionally(err);
+            } else {
+                mSourceResult = res;
+                mExecutor.execute(this);
+            }
+        }
+
+        @Override
+        public void run() {
+            try {
+                complete(mFn.apply(mSourceResult));
+            } catch (Throwable t) {
+                completeExceptionally(t);
+            }
+        }
+    }
+
+    @Override
+    public <U, V> AndroidFuture<V> thenCombine(
+            @NonNull CompletionStage<? extends U> other,
+            @NonNull BiFunction<? super T, ? super U, ? extends V> combineResults) {
+        return new ThenCombine<T, U, V>(this, other, combineResults);
+    }
+
+    /** @see CompletionStage#thenCombine */
+    public AndroidFuture<T> thenCombine(@NonNull CompletionStage<Void> other) {
+        return thenCombine(other, (res, aVoid) -> res);
+    }
+
+    private static class ThenCombine<T, U, V> extends AndroidFuture<V>
+            implements BiConsumer<Object, Throwable> {
+        private volatile @Nullable T mResultT = null;
+        private volatile @NonNull CompletionStage<? extends U> mSourceU;
+        private final @NonNull BiFunction<? super T, ? super U, ? extends V> mCombineResults;
+
+        ThenCombine(CompletableFuture<T> sourceT,
+                CompletionStage<? extends U> sourceU,
+                BiFunction<? super T, ? super U, ? extends V> combineResults) {
+            mSourceU = Preconditions.checkNotNull(sourceU);
+            mCombineResults = Preconditions.checkNotNull(combineResults);
+
+            sourceT.whenComplete(this);
+        }
+
+        @Override
+        public void accept(Object res, Throwable err) {
+            if (err != null) {
+                completeExceptionally(err);
+                return;
+            }
+
+            if (mSourceU != null) {
+                // T done
+                mResultT = (T) res;
+                mSourceU.whenComplete(this);
+            } else {
+                // U done
+                try {
+                    complete(mCombineResults.apply(mResultT, (U) res));
+                } catch (Throwable t) {
+                    completeExceptionally(t);
                 }
             }
         }
     }
+
+    /**
+     * Similar to {@link CompletableFuture#supplyAsync} but
+     * runs the given action directly.
+     *
+     * The resulting future is immediately completed.
+     */
+    public static <T> AndroidFuture<T> supply(Supplier<T> supplier) {
+        return supplyAsync(supplier, DIRECT_EXECUTOR);
+    }
+
+    /**
+     * @see CompletableFuture#supplyAsync(Supplier, Executor)
+     */
+    public static <T> AndroidFuture<T> supplyAsync(Supplier<T> supplier, Executor executor) {
+        return new SupplyAsync<>(supplier, executor);
+    }
+
+    private static class SupplyAsync<T> extends AndroidFuture<T> implements Runnable {
+        private final @NonNull Supplier<T> mSupplier;
+
+        SupplyAsync(Supplier<T> supplier, Executor executor) {
+            mSupplier = supplier;
+            executor.execute(this);
+        }
+
+        @Override
+        public void run() {
+            try {
+                complete(mSupplier.get());
+            } catch (Throwable t) {
+                completeExceptionally(t);
+            }
+        }
+    }
 }
diff --git a/core/java/com/android/internal/infra/RemoteStream.java b/core/java/com/android/internal/infra/RemoteStream.java
new file mode 100644
index 0000000..718788f
--- /dev/null
+++ b/core/java/com/android/internal/infra/RemoteStream.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.infra;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.os.AsyncTask;
+import android.os.ParcelFileDescriptor;
+
+import com.android.internal.util.FunctionalUtils.ThrowingConsumer;
+import com.android.internal.util.FunctionalUtils.ThrowingFunction;
+
+import libcore.io.IoUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.concurrent.Executor;
+
+/**
+ * Utility class for streaming bytes across IPC, using standard APIs such as
+ * {@link InputStream}/{@link OutputStream} or simply {@code byte[]}
+ *
+ * <p>
+ * To use this, you'll want to declare your IPC methods to accept a {@link ParcelFileDescriptor},
+ * and call them from within lambdas passed to {@link #receiveBytes}/{@link #sendBytes},
+ * passing on the provided {@link ParcelFileDescriptor}.
+ *
+ * <p>
+ * E.g.:
+ * {@code
+ *     //IFoo.aidl
+ *     oneway interface IFoo {
+ *         void sendGreetings(in ParcelFileDescriptor pipe);
+ *         void receiveGreetings(in ParcelFileDescriptor pipe);
+ *     }
+ *
+ *     //Foo.java
+ *     mServiceConnector.postAsync(service -> RemoteStream.sendBytes(
+ *             pipe -> service.sendGreetings(pipe, greetings)))...
+ *
+ *     mServiceConnector.postAsync(service -> RemoteStream.receiveBytes(
+ *                    pipe -> service.receiveGreetings(pipe)))
+ *                .whenComplete((greetings, err) -> ...);
+ * }
+ *
+ * <p>
+ * Each operation has a 30 second timeout by default, as it's possible for an operation to be
+ * stuck forever otherwise.
+ * You can {@link #cancelTimeout cancel} and/or {@link #orTimeout set a custom timeout}, using the
+ * {@link AndroidFuture} you get as a result.
+ *
+ * <p>
+ * You can also {@link #cancel} the operation, which will result in closing the underlying
+ * {@link ParcelFileDescriptor}.
+ *
+ * @see #sendBytes
+ * @see #receiveBytes
+ *
+ * @param <RES> the result of a successful streaming.
+ * @param <IOSTREAM> either {@link InputStream} or {@link OutputStream} depending on the direction.
+ */
+public abstract class RemoteStream<RES, IOSTREAM extends Closeable>
+        extends AndroidFuture<RES>
+        implements Runnable {
+
+    private final ThrowingFunction<IOSTREAM, RES> mHandleStream;
+    private volatile ParcelFileDescriptor mLocalPipe;
+
+    /**
+     * Call an IPC, and process incoming bytes as an {@link InputStream} within {@code read}.
+     *
+     * @param ipc action to perform the IPC. Called directly on the calling thread.
+     * @param read action to read from an {@link InputStream}, transforming data into {@code R}.
+     *             Called asynchronously on the background thread.
+     * @param <R> type of the end result of reading the bytes (if any).
+     * @return an {@link AndroidFuture} that can be used to track operation's completion and
+     *         retrieve its result (if any).
+     */
+    public static <R> AndroidFuture<R> receiveBytes(
+            ThrowingConsumer<ParcelFileDescriptor> ipc, ThrowingFunction<InputStream, R> read) {
+        return new RemoteStream<R, InputStream>(
+                ipc, read, AsyncTask.THREAD_POOL_EXECUTOR, true /* read */) {
+            @Override
+            protected InputStream createStream(ParcelFileDescriptor fd) {
+                return new ParcelFileDescriptor.AutoCloseInputStream(fd);
+            }
+        };
+    }
+
+    /**
+     * Call an IPC, and asynchronously return incoming bytes as {@code byte[]}.
+     *
+     * @param ipc action to perform the IPC. Called directly on the calling thread.
+     * @return an {@link AndroidFuture} that can be used to track operation's completion and
+     *         retrieve its result.
+     */
+    public static AndroidFuture<byte[]> receiveBytes(ThrowingConsumer<ParcelFileDescriptor> ipc) {
+        return receiveBytes(ipc, RemoteStream::readAll);
+    }
+
+    /**
+     * Convert a given {@link InputStream} into {@code byte[]}.
+     *
+     * <p>
+     * This doesn't close the given {@link InputStream}
+     */
+    public static byte[] readAll(InputStream inputStream) throws IOException {
+        ByteArrayOutputStream combinedBuffer = new ByteArrayOutputStream();
+        byte[] buffer = new byte[16 * 1024];
+        while (true) {
+            int numRead = inputStream.read(buffer);
+            if (numRead == -1) {
+                break;
+            }
+            combinedBuffer.write(buffer, 0, numRead);
+        }
+        return combinedBuffer.toByteArray();
+    }
+
+    /**
+     * Call an IPC, and perform sending bytes via an {@link OutputStream} within {@code write}.
+     *
+     * @param ipc action to perform the IPC. Called directly on the calling thread.
+     * @param write action to write to an {@link OutputStream}, optionally returning operation
+     *              result as {@code R}. Called asynchronously on the background thread.
+     * @param <R> type of the end result of writing the bytes (if any).
+     * @return an {@link AndroidFuture} that can be used to track operation's completion and
+     *         retrieve its result (if any).
+     */
+    public static <R> AndroidFuture<R> sendBytes(
+            ThrowingConsumer<ParcelFileDescriptor> ipc, ThrowingFunction<OutputStream, R> write) {
+        return new RemoteStream<R, OutputStream>(
+                ipc, write, AsyncTask.THREAD_POOL_EXECUTOR, false /* read */) {
+            @Override
+            protected OutputStream createStream(ParcelFileDescriptor fd) {
+                return new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+            }
+        };
+    }
+
+    /**
+     * Same as {@link #sendBytes(ThrowingConsumer, ThrowingFunction)}, but explicitly avoids
+     * returning a result.
+     */
+    public static AndroidFuture<Void> sendBytes(
+            ThrowingConsumer<ParcelFileDescriptor> ipc, ThrowingConsumer<OutputStream> write) {
+        return sendBytes(ipc, os -> {
+            write.acceptOrThrow(os);
+            return null;
+        });
+    }
+
+    /**
+     * Same as {@link #sendBytes(ThrowingConsumer, ThrowingFunction)}, but providing the data to
+     * send eagerly as {@code byte[]}.
+     */
+    public static AndroidFuture<Void> sendBytes(
+            ThrowingConsumer<ParcelFileDescriptor> ipc, byte[] data) {
+        return sendBytes(ipc, os -> {
+            os.write(data);
+            return null;
+        });
+    }
+
+    private RemoteStream(
+            ThrowingConsumer<ParcelFileDescriptor> ipc,
+            ThrowingFunction<IOSTREAM, RES> handleStream,
+            Executor backgroundExecutor,
+            boolean read) {
+        mHandleStream = handleStream;
+
+        ParcelFileDescriptor[] pipe;
+        try {
+            //TODO consider using createReliablePipe
+            pipe = ParcelFileDescriptor.createPipe();
+            try (ParcelFileDescriptor remotePipe = pipe[read ? 1 : 0]) {
+                ipc.acceptOrThrow(remotePipe);
+                // Remote pipe end is duped by binder call. Local copy is not needed anymore
+            }
+
+            mLocalPipe = pipe[read ? 0 : 1];
+            backgroundExecutor.execute(this);
+
+            // Guard against getting stuck forever
+            orTimeout(30, SECONDS);
+        } catch (Throwable e) {
+            completeExceptionally(e);
+            // mLocalPipe closes in #onCompleted
+        }
+    }
+
+    protected abstract IOSTREAM createStream(ParcelFileDescriptor fd);
+
+    @Override
+    public void run() {
+        try (IOSTREAM stream = createStream(mLocalPipe)) {
+            complete(mHandleStream.applyOrThrow(stream));
+        } catch (Throwable t) {
+            completeExceptionally(t);
+        }
+    }
+
+    @Override
+    protected void onCompleted(RES res, Throwable err) {
+        super.onCompleted(res, err);
+        IoUtils.closeQuietly(mLocalPipe);
+    }
+}
diff --git a/core/java/com/android/internal/infra/ServiceConnector.java b/core/java/com/android/internal/infra/ServiceConnector.java
index ea4a302..283079a 100644
--- a/core/java/com/android/internal/infra/ServiceConnector.java
+++ b/core/java/com/android/internal/infra/ServiceConnector.java
@@ -42,6 +42,7 @@
 import java.util.Queue;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
+import java.util.concurrent.TimeUnit;
 import java.util.function.BiConsumer;
 import java.util.function.Function;
 
@@ -70,7 +71,7 @@
      *
      * @return whether a job was successfully scheduled
      */
-    boolean fireAndForget(@NonNull VoidJob<I> job);
+    boolean run(@NonNull VoidJob<I> job);
 
     /**
      * Schedules to run a given job when service is connected.
@@ -167,7 +168,7 @@
          * @return the result of this operation to be propagated to the original caller.
          *         If you do not need to provide a result you can implement {@link VoidJob} instead
          */
-        R run(@NonNull II service) throws RemoteException;
+        R run(@NonNull II service) throws Exception;
 
     }
 
@@ -180,10 +181,10 @@
     interface VoidJob<II> extends Job<II, Void> {
 
         /** @see Job#run */
-        void runNoResult(II service) throws RemoteException;
+        void runNoResult(II service) throws Exception;
 
         @Override
-        default Void run(II service) throws RemoteException {
+        default Void run(II service) throws Exception {
             runNoResult(service);
             return null;
         }
@@ -213,6 +214,7 @@
         static final String LOG_TAG = "ServiceConnector.Impl";
 
         private static final long DEFAULT_DISCONNECT_TIMEOUT_MS = 15_000;
+        private static final long DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
 
         private final @NonNull Queue<Job<I, ?>> mQueue = this;
         private final @NonNull List<CompletionAwareJob<I, ?>> mUnfinishedJobs = new ArrayList<>();
@@ -275,6 +277,19 @@
         }
 
         /**
+         * Gets the amount of time to wait for a request to complete, before finishing it with a
+         * {@link java.util.concurrent.TimeoutException}
+         *
+         * <p>
+         * This includes time spent connecting to the service, if any.
+         *
+         * @return amount of time in ms
+         */
+        protected long getRequestTimeoutMs() {
+            return DEFAULT_REQUEST_TIMEOUT_MS;
+        }
+
+        /**
          * {@link Context#bindServiceAsUser Binds} to the service.
          *
          * <p>
@@ -320,7 +335,7 @@
         protected void onServiceConnectionStatusChanged(@NonNull I service, boolean isConnected) {}
 
         @Override
-        public boolean fireAndForget(@NonNull VoidJob<I> job) {
+        public boolean run(@NonNull VoidJob<I> job) {
             if (DEBUG) {
                 Log.d(LOG_TAG, "Wrapping fireAndForget job to take advantage of its mDebugName");
                 return !post(job).isCompletedExceptionally();
@@ -653,6 +668,11 @@
             boolean mAsync = false;
             private String mDebugName;
             {
+                long requestTimeout = getRequestTimeoutMs();
+                if (requestTimeout > 0) {
+                    orTimeout(requestTimeout, TimeUnit.MILLISECONDS);
+                }
+
                 if (DEBUG) {
                     mDebugName = Arrays.stream(Thread.currentThread().getStackTrace())
                             .skip(2)
@@ -665,7 +685,7 @@
             }
 
             @Override
-            public R run(@NonNull II service) throws RemoteException {
+            public R run(@NonNull II service) throws Exception {
                 return mDelegate.run(service);
             }
 
@@ -688,15 +708,20 @@
 
             @Override
             public void accept(@Nullable R res, @Nullable Throwable err) {
-                if (mUnfinishedJobs.remove(this)) {
-                    maybeScheduleUnbindTimeout();
-                }
                 if (err != null) {
                     completeExceptionally(err);
                 } else {
                     complete(res);
                 }
             }
+
+            @Override
+            protected void onCompleted(R res, Throwable err) {
+                super.onCompleted(res, err);
+                if (mUnfinishedJobs.remove(this)) {
+                    maybeScheduleUnbindTimeout();
+                }
+            }
         }
     }
 }
diff --git a/core/java/com/android/internal/infra/TEST_MAPPING b/core/java/com/android/internal/infra/TEST_MAPPING
new file mode 100644
index 0000000..3781d63
--- /dev/null
+++ b/core/java/com/android/internal/infra/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsRoleTestCases"
+    },
+    {
+      "name": "CtsPermissionTestCases",
+      "options": [
+          {
+              "include-filter": "android.permission.cts.PermissionControllerTest"
+          }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/util/ConcurrentUtils.java b/core/java/com/android/internal/util/ConcurrentUtils.java
index 8023500..72caad4 100644
--- a/core/java/com/android/internal/util/ConcurrentUtils.java
+++ b/core/java/com/android/internal/util/ConcurrentUtils.java
@@ -21,6 +21,7 @@
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
@@ -38,6 +39,8 @@
     private ConcurrentUtils() {
     }
 
+    public static final Executor DIRECT_EXECUTOR = new DirectExecutor();
+
     /**
      * Creates a thread pool using
      * {@link java.util.concurrent.Executors#newFixedThreadPool(int, ThreadFactory)}
@@ -130,4 +133,17 @@
             Slog.wtf(tag, "Lock must be held");
         }
     }
+
+    private static class DirectExecutor implements Executor {
+
+        @Override
+        public void execute(Runnable command) {
+            command.run();
+        }
+
+        @Override
+        public String toString() {
+            return "DIRECT_EXECUTOR";
+        }
+    }
 }
diff --git a/core/java/com/android/internal/util/FunctionalUtils.java b/core/java/com/android/internal/util/FunctionalUtils.java
index d53090b..b955f67 100644
--- a/core/java/com/android/internal/util/FunctionalUtils.java
+++ b/core/java/com/android/internal/util/FunctionalUtils.java
@@ -20,6 +20,7 @@
 import android.util.ExceptionUtils;
 
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.function.Supplier;
 
 /**
@@ -37,6 +38,27 @@
     }
 
     /**
+     * @see #uncheckExceptions(ThrowingConsumer)
+     */
+    public static <I, O> Function<I, O> uncheckExceptions(ThrowingFunction<I, O> action) {
+        return action;
+    }
+
+    /**
+     * @see #uncheckExceptions(ThrowingConsumer)
+     */
+    public static Runnable uncheckExceptions(ThrowingRunnable action) {
+        return action;
+    }
+
+    /**
+     * @see #uncheckExceptions(ThrowingConsumer)
+     */
+    public static <T> Supplier<T> uncheckExceptions(ThrowingSupplier<T> action) {
+        return action;
+    }
+
+    /**
      * Wraps a given {@code action} into one that ignores any {@link RemoteException}s
      */
     public static <T> Consumer<T> ignoreRemoteException(RemoteExceptionIgnoringConsumer<T> action) {
@@ -85,10 +107,19 @@
      * to be handled within it
      */
     @FunctionalInterface
-    public interface ThrowingSupplier<T> {
+    @SuppressWarnings("FunctionalInterfaceMethodChanged")
+    public interface ThrowingSupplier<T> extends Supplier<T> {
         T getOrThrow() throws Exception;
-    }
 
+        @Override
+        default T get() {
+            try {
+                return getOrThrow();
+            } catch (Exception ex) {
+                throw ExceptionUtils.propagate(ex);
+            }
+        }
+    }
     /**
      * A {@link Consumer} that allows throwing checked exceptions from its single abstract method.
      *
@@ -129,4 +160,29 @@
             }
         }
     }
+
+    /**
+     * A {@link Function} that allows throwing checked exceptions from its single abstract method.
+     *
+     * Can be used together with {@link #uncheckExceptions} to effectively turn a lambda expression
+     * that throws a checked exception into a regular {@link Function}
+     *
+     * @param <T> see {@link Function}
+     * @param <R> see {@link Function}
+     */
+    @FunctionalInterface
+    @SuppressWarnings("FunctionalInterfaceMethodChanged")
+    public interface ThrowingFunction<T, R> extends Function<T, R> {
+        /** @see ThrowingFunction */
+        R applyOrThrow(T t) throws Exception;
+
+        @Override
+        default R apply(T t) {
+            try {
+                return applyOrThrow(t);
+            } catch (Exception ex) {
+                throw ExceptionUtils.propagate(ex);
+            }
+        }
+    }
 }
diff --git a/core/java/com/android/internal/util/ObjectUtils.java b/core/java/com/android/internal/util/ObjectUtils.java
index 59e5a64..a477688 100644
--- a/core/java/com/android/internal/util/ObjectUtils.java
+++ b/core/java/com/android/internal/util/ObjectUtils.java
@@ -24,11 +24,20 @@
 public class ObjectUtils {
     private ObjectUtils() {}
 
+    /**
+     * Returns the first of two given parameters that is not {@code null}, if either is,
+     * or otherwise throws a {@link NullPointerException}.
+     *
+     * @throws NullPointerException if both {@code a} and {@code b} were {@code null}
+     */
     @NonNull
     public static <T> T firstNotNull(@Nullable T a, @NonNull T b) {
         return a != null ? a : Preconditions.checkNotNull(b);
     }
 
+    /**
+     * Nullsafe {@link Comparable#compareTo}
+     */
     public static <T extends Comparable> int compare(@Nullable T a, @Nullable T b) {
         if (a != null) {
             return (b != null) ? a.compareTo(b) : 1;
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 1b7dd86..3d7738e 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -224,7 +224,7 @@
      * Called by {@link Session} when it's time to destroy all augmented autofill requests.
      */
     public void onDestroyAutofillWindowsRequest() {
-        fireAndForget((s) -> s.onDestroyAllFillWindowsRequest());
+        run((s) -> s.onDestroyAllFillWindowsRequest());
     }
 
     public interface RemoteAugmentedAutofillServiceCallbacks