diff options
| author | 2019-05-07 18:30:48 -0700 | |
|---|---|---|
| committer | 2019-05-13 18:56:00 -0700 | |
| commit | be4e3af8528fbd16457757b96372e674d25c4204 (patch) | |
| tree | 7bc4ea346d8942d4b5366ef22f574abb189fb5a9 | |
| parent | 15b601425213887dde2cdbdd2641d0d9fd8259a1 (diff) | |
Migrate PermissionControllerManager to ServiceConnector
Test: - atest --test-mapping core/java/com/android/internal/infra
- m -j CtsBackupHostTestCases && atest android.backup.cts.PermissionTest
Change-Id: I6a590194207d08569f41f3c5ac6d56e63737feaa
10 files changed, 799 insertions, 835 deletions
diff --git a/core/java/android/app/role/TEST_MAPPING b/core/java/android/app/role/TEST_MAPPING new file mode 100644 index 000000000000..11c28032c7cf --- /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 6e83e5a1f8b6..de0bcb628f5e 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.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; @@ -41,41 +39,35 @@ import android.annotation.SystemService; 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 @@ import java.util.function.Consumer; 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 @@ public final class PermissionControllerManager { } 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 @@ public final class PermissionControllerManager { 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) { + + @Override + protected Handler getJobHandler() { + return handler; + } - remoteService = new RemoteService(ActivityThread.currentApplication(), - serviceInfo.getComponentInfo().getComponentName(), handler, - context.getUser()); + @Override + protected long getAutoDisconnectTimeoutMs() { + return UNBIND_TIMEOUT_MILLIS; + } + }; sRemoteServices.put(key, remoteService); } @@ -243,6 +252,7 @@ public final class PermissionControllerManager { } mContext = context; + mHandler = handler; } /** @@ -274,8 +284,46 @@ public final class PermissionControllerManager { + " 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 @@ public final class PermissionControllerManager { 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 @@ public final class PermissionControllerManager { 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 @@ public final class PermissionControllerManager { 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 @@ public final class PermissionControllerManager { 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 @@ public final class PermissionControllerManager { @NonNull OnGetAppPermissionResultCallback callback, @Nullable Handler handler) { checkNotNull(packageName); checkNotNull(callback); - - mRemoteService.scheduleRequest(new PendingGetAppPermissionRequest(mRemoteService, - packageName, callback, handler == null ? mRemoteService.getHandler() : handler)); + Handler finalHandler = handler != null ? handler : mHandler; + + 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 @@ public final class PermissionControllerManager { checkNotNull(packageName); checkNotNull(permissionName); - mRemoteService.scheduleAsyncRequest(new PendingRevokeAppPermissionRequest(packageName, - permissionName)); + mRemoteService.run(service -> service.revokeRuntimePermission(packageName, permissionName)); } /** @@ -431,10 +545,23 @@ public final class PermissionControllerManager { checkCollectionElementsNotNull(permissionNames, "permissionNames"); checkFlagsArgument(flags, COUNT_WHEN_SYSTEM | COUNT_ONLY_WHEN_GRANTED); checkNotNull(callback); - - mRemoteService.scheduleRequest(new PendingCountPermissionAppsRequest(mRemoteService, - permissionNames, flags, callback, - handler == null ? mRemoteService.getHandler() : handler)); + Handler finalHandler = handler != null ? handler : mHandler; + + 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 @@ public final class PermissionControllerManager { 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 @@ public final class PermissionControllerManager { @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; - } - - 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); + 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); } - } - } - - /** - * 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 a459de44b835..c9e2d5fe8f8c 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 com.android.internal.util.function.pooled.PooledLambda; 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 @@ import java.util.function.Function; * 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 @@ public class AndroidFuture<T> extends CompletableFuture<T> { 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 @@ public class AndroidFuture<T> extends CompletableFuture<T> { 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 @@ public class AndroidFuture<T> extends CompletableFuture<T> { } 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 @@ public class AndroidFuture<T> extends CompletableFuture<T> { } 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 @@ public class AndroidFuture<T> extends CompletableFuture<T> { } } - 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 @@ public class AndroidFuture<T> extends CompletableFuture<T> { @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; + if (err != null) { + // first or second job failed + completeExceptionally(err); + } else if (mFn != null) { + // first job completed + mSourceResult = (T) res; + // subscribe to second job completion asynchronously + mExecutor.execute(this); + } else { + // second job completed + 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; } - if (fn != null) { - // first job completed - CompletionStage<U> secondJob; + // 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 { - secondJob = Preconditions.checkNotNull(fn.apply((T) res)); + complete(mCombineResults.apply(mResultT, (U) res)); } catch (Throwable t) { completeExceptionally(t); - return; - } - // subscribe to second job completion - secondJob.whenComplete(this); - } else { - // second job completed - if (err != null) { - completeExceptionally(err); - } else { - complete(res); } } } } + + /** + * 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 000000000000..718788f99448 --- /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 ea4a3021b773..283079a9a5b9 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.List; 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 @@ public interface ServiceConnector<I extends IInterface> { * * @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 @@ public interface ServiceConnector<I extends IInterface> { * @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 @@ public interface ServiceConnector<I extends IInterface> { 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 @@ public interface ServiceConnector<I extends IInterface> { 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 @@ public interface ServiceConnector<I extends IInterface> { } /** + * 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 @@ public interface ServiceConnector<I extends IInterface> { 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 @@ public interface ServiceConnector<I extends IInterface> { 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 @@ public interface ServiceConnector<I extends IInterface> { } @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 @@ public interface ServiceConnector<I extends IInterface> { @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 000000000000..3781d637f68c --- /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 8023500b00e4..72caad410a27 100644 --- a/core/java/com/android/internal/util/ConcurrentUtils.java +++ b/core/java/com/android/internal/util/ConcurrentUtils.java @@ -21,6 +21,7 @@ import android.util.Slog; 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 @@ public class ConcurrentUtils { 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 @@ public class ConcurrentUtils { 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 d53090b4678c..b955f672fe93 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.os.RemoteException; import android.util.ExceptionUtils; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Supplier; /** @@ -37,6 +38,27 @@ public class FunctionalUtils { } /** + * @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 @@ public class FunctionalUtils { * 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 @@ public class FunctionalUtils { } } } + + /** + * 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 59e5a6402fb8..a47768870dfe 100644 --- a/core/java/com/android/internal/util/ObjectUtils.java +++ b/core/java/com/android/internal/util/ObjectUtils.java @@ -24,11 +24,20 @@ import android.annotation.Nullable; 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 1b7dd8682f30..3d7738e69e43 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 @@ final class RemoteAugmentedAutofillService * 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 |