Merge "Replace RemoteCallback with AndroidFuture in PermControler"
diff --git a/Android.bp b/Android.bp
index e70c707..8ed86d3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -421,6 +421,7 @@
"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",
diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl
index 45c01bc..ec0fe92 100644
--- a/core/java/android/permission/IPermissionController.aidl
+++ b/core/java/android/permission/IPermissionController.aidl
@@ -20,6 +20,7 @@
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 @@
*/
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);
}
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index de0bcb6..566ba8a 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -46,7 +46,6 @@
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.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.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 @@
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 @@
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 @@
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 @@
err);
callback.accept(false);
} else {
- callback.accept(
- setRuntimePermissionGrantStateResult.getBoolean(KEY_RESULT, false));
+ callback.accept(setRuntimePermissionGrantStateResult);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -453,10 +426,10 @@
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 @@
callback.accept(true);
} else {
callback.accept(
- restoreDelayedRuntimePermissionBackupResult
- .getBoolean(KEY_RESULT, false));
+ Boolean.TRUE.equals(restoreDelayedRuntimePermissionBackupResult));
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -492,20 +464,16 @@
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 @@
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 @@
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 @@
} 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 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);
}
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 8ae93a7..c9a1c38 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -41,12 +41,13 @@
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 @@
@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 @@
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 @@
@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 @@
@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 @@
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));
}
};
}
diff --git a/core/java/com/android/internal/infra/AndroidFuture.aidl b/core/java/com/android/internal/infra/AndroidFuture.aidl
new file mode 100644
index 0000000..b19aab8
--- /dev/null
+++ b/core/java/com/android/internal/infra/AndroidFuture.aidl
@@ -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;
diff --git a/core/java/com/android/internal/infra/AndroidFuture.java b/core/java/com/android/internal/infra/AndroidFuture.java
index c9e2d5f..08938a6 100644
--- a/core/java/com/android/internal/infra/AndroidFuture.java
+++ b/core/java/com/android/internal/infra/AndroidFuture.java
@@ -23,6 +23,9 @@
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 @@
* 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 @@
* </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 @@
@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 @@
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 @@
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 @@
}
}
}
+
+ @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];
+ }
+ };
}
diff --git a/core/java/com/android/internal/infra/IAndroidFuture.aidl b/core/java/com/android/internal/infra/IAndroidFuture.aidl
new file mode 100644
index 0000000..302fdde
--- /dev/null
+++ b/core/java/com/android/internal/infra/IAndroidFuture.aidl
@@ -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);
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 78fdfe4..f9cf23b 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -18,6 +18,7 @@
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.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;
@@ -328,6 +330,33 @@
}
/**
+ * 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
*/
public static @Nullable <T> T firstOrNull(@Nullable List<T> cur) {