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