Replace RemoteCallback with AndroidFuture in PermControler
- saves ~3 allocation per ipc - no longer need to [un]bundleize result - will become typesafe down the road (b/132732312) Test: atest android.permission.cts.PermissionControllerTest Change-Id: I6cd9ec5ae31179474536f22f557afb0d9db6a4b9
This commit is contained in:
@@ -421,6 +421,7 @@ java_defaults {
|
||||
"core/java/com/android/internal/appwidget/IAppWidgetHost.aidl",
|
||||
"core/java/com/android/internal/backup/IBackupTransport.aidl",
|
||||
"core/java/com/android/internal/backup/IObbBackupService.aidl",
|
||||
"core/java/com/android/internal/infra/IAndroidFuture.aidl",
|
||||
"core/java/com/android/internal/inputmethod/IInputContentUriToken.aidl",
|
||||
"core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl",
|
||||
"core/java/com/android/internal/inputmethod/IMultiClientInputMethod.aidl",
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.os.RemoteCallback;
|
||||
import android.os.Bundle;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.UserHandle;
|
||||
import com.android.internal.infra.AndroidFuture;
|
||||
|
||||
/**
|
||||
* Interface for system apps to communication with the permission controller.
|
||||
@@ -28,17 +29,17 @@ import android.os.UserHandle;
|
||||
*/
|
||||
oneway interface IPermissionController {
|
||||
void revokeRuntimePermissions(in Bundle request, boolean doDryRun, int reason,
|
||||
String callerPackageName, in RemoteCallback callback);
|
||||
String callerPackageName, in AndroidFuture callback);
|
||||
void getRuntimePermissionBackup(in UserHandle user, in ParcelFileDescriptor pipe);
|
||||
void restoreRuntimePermissionBackup(in UserHandle user, in ParcelFileDescriptor pipe);
|
||||
void restoreDelayedRuntimePermissionBackup(String packageName, in UserHandle user,
|
||||
in RemoteCallback callback);
|
||||
void getAppPermissions(String packageName, in RemoteCallback callback);
|
||||
in AndroidFuture callback);
|
||||
void getAppPermissions(String packageName, in AndroidFuture callback);
|
||||
void revokeRuntimePermission(String packageName, String permissionName);
|
||||
void countPermissionApps(in List<String> permissionNames, int flags,
|
||||
in RemoteCallback callback);
|
||||
void getPermissionUsages(boolean countSystem, long numMillis, in RemoteCallback callback);
|
||||
in AndroidFuture callback);
|
||||
void getPermissionUsages(boolean countSystem, long numMillis, in AndroidFuture callback);
|
||||
void setRuntimePermissionGrantStateByDeviceAdmin(String callerPackageName, String packageName,
|
||||
String permission, int grantState, in RemoteCallback callback);
|
||||
void grantOrUpgradeDefaultRuntimePermissions(in RemoteCallback callback);
|
||||
String permission, int grantState, in AndroidFuture callback);
|
||||
void grantOrUpgradeDefaultRuntimePermissions(in AndroidFuture callback);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ import android.content.pm.ResolveInfo;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteCallback;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
@@ -57,7 +56,6 @@ 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.util.EmptyArray;
|
||||
|
||||
@@ -67,7 +65,6 @@ 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;
|
||||
|
||||
@@ -94,14 +91,6 @@ public final class PermissionControllerManager {
|
||||
private static ArrayMap<Pair<Integer, Thread>, ServiceConnector<IPermissionController>>
|
||||
sRemoteServices = new ArrayMap<>(1);
|
||||
|
||||
/**
|
||||
* The key for retrieving the result from the returned bundle.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final String KEY_RESULT =
|
||||
"android.permission.PermissionControllerManager.key.result";
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = { "REASON_" }, value = {
|
||||
REASON_MALWARE,
|
||||
@@ -291,31 +280,17 @@ public final class PermissionControllerManager {
|
||||
new ArrayList<>(appRequest.getValue()));
|
||||
}
|
||||
|
||||
AndroidFuture<Bundle> revokeRuntimePermissionsResult = new AndroidFuture<>();
|
||||
AndroidFuture<Map<String, List<String>>> revokeRuntimePermissionsResult =
|
||||
new AndroidFuture<>();
|
||||
service.revokeRuntimePermissions(bundledizedRequest, doDryRun, reason,
|
||||
mContext.getPackageName(),
|
||||
new RemoteCallback(revokeRuntimePermissionsResult::complete));
|
||||
revokeRuntimePermissionsResult);
|
||||
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);
|
||||
Log.e(TAG, "Failure when revoking runtime permissions " + revoked, err);
|
||||
callback.onRevokeRuntimePermissions(Collections.emptyMap());
|
||||
} else {
|
||||
callback.onRevokeRuntimePermissions(revoked);
|
||||
@@ -356,11 +331,10 @@ public final class PermissionControllerManager {
|
||||
checkNotNull(callback);
|
||||
|
||||
mRemoteService.postAsync(service -> {
|
||||
CompletableFuture<Bundle> setRuntimePermissionGrantStateResult =
|
||||
new CompletableFuture<>();
|
||||
AndroidFuture<Boolean> setRuntimePermissionGrantStateResult = new AndroidFuture<>();
|
||||
service.setRuntimePermissionGrantStateByDeviceAdmin(
|
||||
callerPackageName, packageName, permission, grantState,
|
||||
new RemoteCallback(setRuntimePermissionGrantStateResult::complete));
|
||||
setRuntimePermissionGrantStateResult);
|
||||
return setRuntimePermissionGrantStateResult;
|
||||
}).whenCompleteAsync((setRuntimePermissionGrantStateResult, err) -> {
|
||||
long token = Binder.clearCallingIdentity();
|
||||
@@ -370,8 +344,7 @@ public final class PermissionControllerManager {
|
||||
err);
|
||||
callback.accept(false);
|
||||
} else {
|
||||
callback.accept(
|
||||
setRuntimePermissionGrantStateResult.getBoolean(KEY_RESULT, false));
|
||||
callback.accept(setRuntimePermissionGrantStateResult);
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
@@ -453,10 +426,10 @@ public final class PermissionControllerManager {
|
||||
checkNotNull(callback);
|
||||
|
||||
mRemoteService.postAsync(service -> {
|
||||
CompletableFuture<Bundle> restoreDelayedRuntimePermissionBackupResult =
|
||||
new CompletableFuture<>();
|
||||
AndroidFuture<Boolean> restoreDelayedRuntimePermissionBackupResult =
|
||||
new AndroidFuture<>();
|
||||
service.restoreDelayedRuntimePermissionBackup(packageName, user,
|
||||
new RemoteCallback(restoreDelayedRuntimePermissionBackupResult::complete));
|
||||
restoreDelayedRuntimePermissionBackupResult);
|
||||
return restoreDelayedRuntimePermissionBackupResult;
|
||||
}).whenCompleteAsync((restoreDelayedRuntimePermissionBackupResult, err) -> {
|
||||
long token = Binder.clearCallingIdentity();
|
||||
@@ -466,8 +439,7 @@ public final class PermissionControllerManager {
|
||||
callback.accept(true);
|
||||
} else {
|
||||
callback.accept(
|
||||
restoreDelayedRuntimePermissionBackupResult
|
||||
.getBoolean(KEY_RESULT, false));
|
||||
Boolean.TRUE.equals(restoreDelayedRuntimePermissionBackupResult));
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
@@ -492,20 +464,16 @@ public final class PermissionControllerManager {
|
||||
Handler finalHandler = handler != null ? handler : mHandler;
|
||||
|
||||
mRemoteService.postAsync(service -> {
|
||||
CompletableFuture<Bundle> getAppPermissionsResult = new CompletableFuture<>();
|
||||
service.getAppPermissions(packageName,
|
||||
new RemoteCallback(getAppPermissionsResult::complete));
|
||||
AndroidFuture<List<RuntimePermissionPresentationInfo>> getAppPermissionsResult =
|
||||
new AndroidFuture<>();
|
||||
service.getAppPermissions(packageName, getAppPermissionsResult);
|
||||
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));
|
||||
callback.onGetAppPermissions(CollectionUtils.emptyIfNull(getAppPermissionsResult));
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -548,18 +516,15 @@ public final class PermissionControllerManager {
|
||||
Handler finalHandler = handler != null ? handler : mHandler;
|
||||
|
||||
mRemoteService.postAsync(service -> {
|
||||
CompletableFuture<Bundle> countPermissionAppsResult = new CompletableFuture<>();
|
||||
service.countPermissionApps(permissionNames, flags,
|
||||
new RemoteCallback(countPermissionAppsResult::complete));
|
||||
AndroidFuture<Integer> countPermissionAppsResult = new AndroidFuture<>();
|
||||
service.countPermissionApps(permissionNames, flags, countPermissionAppsResult);
|
||||
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);
|
||||
callback.onCountPermissionApps(countPermissionAppsResult);
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -584,9 +549,9 @@ public final class PermissionControllerManager {
|
||||
|
||||
|
||||
mRemoteService.postAsync(service -> {
|
||||
CompletableFuture<Bundle> getPermissionUsagesResult = new CompletableFuture<>();
|
||||
service.getPermissionUsages(countSystem, numMillis,
|
||||
new RemoteCallback(getPermissionUsagesResult::complete));
|
||||
AndroidFuture<List<RuntimePermissionUsageInfo>> getPermissionUsagesResult =
|
||||
new AndroidFuture<>();
|
||||
service.getPermissionUsages(countSystem, numMillis, getPermissionUsagesResult);
|
||||
return getPermissionUsagesResult;
|
||||
}).whenCompleteAsync((getPermissionUsagesResult, err) -> {
|
||||
if (err != null) {
|
||||
@@ -595,9 +560,8 @@ public final class PermissionControllerManager {
|
||||
} else {
|
||||
long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
callback.onPermissionUsageResult(getPermissionUsagesResult != null
|
||||
? getPermissionUsagesResult.getParcelableArrayList(KEY_RESULT)
|
||||
: Collections.emptyList());
|
||||
callback.onPermissionUsageResult(
|
||||
CollectionUtils.emptyIfNull(getPermissionUsagesResult));
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
@@ -619,17 +583,17 @@ public final class PermissionControllerManager {
|
||||
public void grantOrUpgradeDefaultRuntimePermissions(
|
||||
@NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
|
||||
mRemoteService.postAsync(service -> {
|
||||
CompletableFuture<Bundle> grantOrUpgradeDefaultRuntimePermissionsResult =
|
||||
new CompletableFuture<>();
|
||||
AndroidFuture<Boolean> grantOrUpgradeDefaultRuntimePermissionsResult =
|
||||
new AndroidFuture<>();
|
||||
service.grantOrUpgradeDefaultRuntimePermissions(
|
||||
new RemoteCallback(grantOrUpgradeDefaultRuntimePermissionsResult::complete));
|
||||
grantOrUpgradeDefaultRuntimePermissionsResult);
|
||||
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);
|
||||
callback.accept(grantOrUpgradeDefaultRuntimePermissionsResult);
|
||||
}
|
||||
}, executor);
|
||||
}
|
||||
|
||||
@@ -41,12 +41,13 @@ import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteCallback;
|
||||
import android.os.UserHandle;
|
||||
import android.permission.PermissionControllerManager.CountPermissionAppsFlag;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.infra.AndroidFuture;
|
||||
import com.android.internal.util.CollectionUtils;
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -209,7 +210,7 @@ public abstract class PermissionControllerService extends Service {
|
||||
@Override
|
||||
public void revokeRuntimePermissions(
|
||||
Bundle bundleizedRequest, boolean doDryRun, int reason,
|
||||
String callerPackageName, RemoteCallback callback) {
|
||||
String callerPackageName, AndroidFuture callback) {
|
||||
checkNotNull(bundleizedRequest, "bundleizedRequest");
|
||||
checkNotNull(callerPackageName);
|
||||
checkNotNull(callback);
|
||||
@@ -237,22 +238,11 @@ public abstract class PermissionControllerService extends Service {
|
||||
|
||||
onRevokeRuntimePermissions(request,
|
||||
doDryRun, reason, callerPackageName, revoked -> {
|
||||
checkNotNull(revoked);
|
||||
Bundle bundledizedRevoked = new Bundle();
|
||||
for (Map.Entry<String, List<String>> appRevocation :
|
||||
revoked.entrySet()) {
|
||||
checkNotNull(appRevocation.getKey());
|
||||
checkCollectionElementsNotNull(appRevocation.getValue(),
|
||||
"permissions");
|
||||
|
||||
bundledizedRevoked.putStringArrayList(appRevocation.getKey(),
|
||||
new ArrayList<>(appRevocation.getValue()));
|
||||
}
|
||||
|
||||
Bundle result = new Bundle();
|
||||
result.putBundle(PermissionControllerManager.KEY_RESULT,
|
||||
bundledizedRevoked);
|
||||
callback.sendResult(result);
|
||||
CollectionUtils.forEach(revoked, (pkg, perms) -> {
|
||||
Preconditions.checkNotNull(pkg);
|
||||
Preconditions.checkCollectionElementsNotNull(perms, "permissions");
|
||||
});
|
||||
callback.complete(revoked);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -294,40 +284,24 @@ public abstract class PermissionControllerService extends Service {
|
||||
|
||||
@Override
|
||||
public void restoreDelayedRuntimePermissionBackup(String packageName, UserHandle user,
|
||||
RemoteCallback callback) {
|
||||
AndroidFuture callback) {
|
||||
checkNotNull(packageName);
|
||||
checkNotNull(user);
|
||||
checkNotNull(callback);
|
||||
|
||||
enforceCallingPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS, null);
|
||||
|
||||
onRestoreDelayedRuntimePermissionsBackup(packageName, user,
|
||||
hasMoreBackup -> {
|
||||
Bundle result = new Bundle();
|
||||
result.putBoolean(PermissionControllerManager.KEY_RESULT,
|
||||
hasMoreBackup);
|
||||
callback.sendResult(result);
|
||||
});
|
||||
onRestoreDelayedRuntimePermissionsBackup(packageName, user, callback::complete);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getAppPermissions(String packageName, RemoteCallback callback) {
|
||||
public void getAppPermissions(String packageName, AndroidFuture callback) {
|
||||
checkNotNull(packageName, "packageName");
|
||||
checkNotNull(callback, "callback");
|
||||
|
||||
enforceCallingPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS, null);
|
||||
|
||||
onGetAppPermissions(packageName,
|
||||
permissions -> {
|
||||
if (permissions != null && !permissions.isEmpty()) {
|
||||
Bundle result = new Bundle();
|
||||
result.putParcelableList(PermissionControllerManager.KEY_RESULT,
|
||||
permissions);
|
||||
callback.sendResult(result);
|
||||
} else {
|
||||
callback.sendResult(null);
|
||||
}
|
||||
});
|
||||
onGetAppPermissions(packageName, callback::complete);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -349,43 +323,31 @@ public abstract class PermissionControllerService extends Service {
|
||||
|
||||
@Override
|
||||
public void countPermissionApps(List<String> permissionNames, int flags,
|
||||
RemoteCallback callback) {
|
||||
AndroidFuture callback) {
|
||||
checkCollectionElementsNotNull(permissionNames, "permissionNames");
|
||||
checkFlagsArgument(flags, COUNT_WHEN_SYSTEM | COUNT_ONLY_WHEN_GRANTED);
|
||||
checkNotNull(callback, "callback");
|
||||
|
||||
enforceCallingPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS, null);
|
||||
|
||||
onCountPermissionApps(permissionNames, flags, numApps -> {
|
||||
Bundle result = new Bundle();
|
||||
result.putInt(PermissionControllerManager.KEY_RESULT, numApps);
|
||||
callback.sendResult(result);
|
||||
});
|
||||
onCountPermissionApps(permissionNames, flags, callback::complete);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getPermissionUsages(boolean countSystem, long numMillis,
|
||||
RemoteCallback callback) {
|
||||
AndroidFuture callback) {
|
||||
checkArgumentNonnegative(numMillis);
|
||||
checkNotNull(callback, "callback");
|
||||
|
||||
enforceCallingPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS, null);
|
||||
|
||||
onGetPermissionUsages(countSystem, numMillis, users -> {
|
||||
if (users != null && !users.isEmpty()) {
|
||||
Bundle result = new Bundle();
|
||||
result.putParcelableList(PermissionControllerManager.KEY_RESULT, users);
|
||||
callback.sendResult(result);
|
||||
} else {
|
||||
callback.sendResult(null);
|
||||
}
|
||||
});
|
||||
onGetPermissionUsages(countSystem, numMillis, callback::complete);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRuntimePermissionGrantStateByDeviceAdmin(String callerPackageName,
|
||||
String packageName, String permission, int grantState,
|
||||
RemoteCallback callback) {
|
||||
AndroidFuture callback) {
|
||||
checkStringNotEmpty(callerPackageName);
|
||||
checkStringNotEmpty(packageName);
|
||||
checkStringNotEmpty(permission);
|
||||
@@ -406,21 +368,17 @@ public abstract class PermissionControllerService extends Service {
|
||||
null);
|
||||
|
||||
onSetRuntimePermissionGrantStateByDeviceAdmin(callerPackageName,
|
||||
packageName, permission, grantState, wasSet -> {
|
||||
Bundle result = new Bundle();
|
||||
result.putBoolean(PermissionControllerManager.KEY_RESULT, wasSet);
|
||||
callback.sendResult(result);
|
||||
});
|
||||
packageName, permission, grantState, callback::complete);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void grantOrUpgradeDefaultRuntimePermissions(@NonNull RemoteCallback callback) {
|
||||
public void grantOrUpgradeDefaultRuntimePermissions(@NonNull AndroidFuture callback) {
|
||||
checkNotNull(callback, "callback");
|
||||
|
||||
enforceCallingPermission(Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY,
|
||||
null);
|
||||
|
||||
onGrantOrUpgradeDefaultRuntimePermissions(() -> callback.sendResult(Bundle.EMPTY));
|
||||
onGrantOrUpgradeDefaultRuntimePermissions(() -> callback.complete(null));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
20
core/java/com/android/internal/infra/AndroidFuture.aidl
Normal file
20
core/java/com/android/internal/infra/AndroidFuture.aidl
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
** Copyright 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;
|
||||
|
||||
/** @hide */
|
||||
parcelable AndroidFuture;
|
||||
@@ -23,6 +23,9 @@ import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.os.RemoteException;
|
||||
import android.util.ExceptionUtils;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -45,6 +48,7 @@ import java.util.function.Supplier;
|
||||
* A customized {@link CompletableFuture} with focus on reducing the number of allocations involved
|
||||
* in a typical future usage scenario for Android.
|
||||
*
|
||||
* <p>
|
||||
* In particular this involves allocations optimizations in:
|
||||
* <ul>
|
||||
* <li>{@link #thenCompose(Function)}</li>
|
||||
@@ -55,10 +59,20 @@ import java.util.function.Supplier;
|
||||
* </ul>
|
||||
* As well as their *Async versions.
|
||||
*
|
||||
* <p>
|
||||
* You can pass {@link AndroidFuture} across an IPC.
|
||||
* When doing so, completing the future on the other side will propagate the completion back,
|
||||
* effectively acting as an error-aware remote callback.
|
||||
*
|
||||
* <p>
|
||||
* {@link AndroidFuture} is {@link Parcelable} iff its wrapped type {@code T} is
|
||||
* effectively parcelable, i.e. is supported by {@link Parcel#readValue}/{@link Parcel#writeValue}.
|
||||
*
|
||||
* @param <T> see {@link CompletableFuture}
|
||||
*/
|
||||
public class AndroidFuture<T> extends CompletableFuture<T> {
|
||||
public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
private static final String LOG_TAG = AndroidFuture.class.getSimpleName();
|
||||
|
||||
private final @NonNull Object mLock = new Object();
|
||||
@@ -67,6 +81,38 @@ public class AndroidFuture<T> extends CompletableFuture<T> {
|
||||
@GuardedBy("mLock")
|
||||
private @Nullable Executor mListenerExecutor = DIRECT_EXECUTOR;
|
||||
private @NonNull Handler mTimeoutHandler = Handler.getMain();
|
||||
private final @Nullable IAndroidFuture mRemoteOrigin;
|
||||
|
||||
public AndroidFuture() {
|
||||
super();
|
||||
mRemoteOrigin = null;
|
||||
}
|
||||
|
||||
AndroidFuture(Parcel in) {
|
||||
super();
|
||||
if (in.readBoolean()) {
|
||||
// Done
|
||||
if (in.readBoolean()) {
|
||||
// Failed
|
||||
try {
|
||||
in.readException();
|
||||
} catch (Throwable e) {
|
||||
completeExceptionally(e);
|
||||
}
|
||||
if (!isCompletedExceptionally()) {
|
||||
throw new IllegalStateException(
|
||||
"Error unparceling AndroidFuture: exception expected");
|
||||
}
|
||||
} else {
|
||||
// Success
|
||||
complete((T) in.readValue(null));
|
||||
}
|
||||
mRemoteOrigin = null;
|
||||
} else {
|
||||
// Not done
|
||||
mRemoteOrigin = IAndroidFuture.Stub.asInterface(in.readStrongBinder());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean complete(@Nullable T value) {
|
||||
@@ -90,6 +136,11 @@ public class AndroidFuture<T> extends CompletableFuture<T> {
|
||||
protected void onCompleted(@Nullable T res, @Nullable Throwable err) {
|
||||
cancelTimeout();
|
||||
|
||||
if (DEBUG) {
|
||||
Log.i(LOG_TAG, this + " completed with result " + (err == null ? res : err),
|
||||
new RuntimeException());
|
||||
}
|
||||
|
||||
BiConsumer<? super T, ? super Throwable> listener;
|
||||
synchronized (mLock) {
|
||||
listener = mListener;
|
||||
@@ -99,6 +150,14 @@ public class AndroidFuture<T> extends CompletableFuture<T> {
|
||||
if (listener != null) {
|
||||
callListenerAsync(listener, res, err);
|
||||
}
|
||||
|
||||
if (mRemoteOrigin != null) {
|
||||
try {
|
||||
mRemoteOrigin.complete(this /* resultContainer */);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(LOG_TAG, "Failed to propagate completion", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -413,4 +472,49 @@ public class AndroidFuture<T> extends CompletableFuture<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
boolean done = isDone();
|
||||
dest.writeBoolean(done);
|
||||
if (done) {
|
||||
T result;
|
||||
try {
|
||||
result = get();
|
||||
} catch (Exception t) {
|
||||
dest.writeBoolean(true);
|
||||
dest.writeException(t);
|
||||
return;
|
||||
}
|
||||
dest.writeBoolean(false);
|
||||
dest.writeValue(result);
|
||||
} else {
|
||||
dest.writeStrongBinder(new IAndroidFuture.Stub() {
|
||||
@Override
|
||||
public void complete(AndroidFuture resultContainer) {
|
||||
try {
|
||||
AndroidFuture.this.complete((T) resultContainer.get());
|
||||
} catch (Throwable t) {
|
||||
completeExceptionally(t);
|
||||
}
|
||||
}
|
||||
}.asBinder());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final @NonNull Parcelable.Creator<AndroidFuture> CREATOR =
|
||||
new Parcelable.Creator<AndroidFuture>() {
|
||||
public AndroidFuture createFromParcel(Parcel parcel) {
|
||||
return new AndroidFuture(parcel);
|
||||
}
|
||||
|
||||
public AndroidFuture[] newArray(int size) {
|
||||
return new AndroidFuture[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
23
core/java/com/android/internal/infra/IAndroidFuture.aidl
Normal file
23
core/java/com/android/internal/infra/IAndroidFuture.aidl
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
** Copyright 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 com.android.internal.infra.AndroidFuture;
|
||||
|
||||
oneway interface IAndroidFuture {
|
||||
void complete(in AndroidFuture resultContainer);
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package com.android.internal.util;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.ExceptionUtils;
|
||||
|
||||
@@ -29,6 +30,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
@@ -327,6 +329,33 @@ public class CollectionUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies {@code action} to each element in {@code cur}
|
||||
*
|
||||
* This avoids creating an iterator if the given map is an {@link ArrayMap}
|
||||
* For non-{@link ArrayMap}s it avoids creating {@link Map.Entry} instances
|
||||
*/
|
||||
public static <K, V> void forEach(@Nullable Map<K, V> cur, @Nullable BiConsumer<K, V> action) {
|
||||
if (cur == null || action == null) {
|
||||
return;
|
||||
}
|
||||
int size = cur.size();
|
||||
if (size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cur instanceof ArrayMap) {
|
||||
ArrayMap<K, V> arrayMap = (ArrayMap<K, V>) cur;
|
||||
for (int i = 0; i < size; i++) {
|
||||
action.accept(arrayMap.keyAt(i), arrayMap.valueAt(i));
|
||||
}
|
||||
} else {
|
||||
for (K key : cur.keySet()) {
|
||||
action.accept(key, cur.get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the first element if not empty/null, null otherwise
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user