/*
* 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 android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Handler;
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import java.lang.reflect.Constructor;
import java.util.concurrent.CancellationException;
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
* in a typical future usage scenario for Android.
*
*
* In particular this involves allocations optimizations in:
*
*
{@link #thenCompose(Function)}
*
{@link #thenApply(Function)}
*
{@link #thenCombine(CompletionStage, BiFunction)}
*
{@link #orTimeout(long, TimeUnit)}
*
{@link #whenComplete(BiConsumer)}
*
* As well as their *Async versions.
*
*
* 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.
*
*
* {@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 see {@link CompletableFuture}
*/
public class AndroidFuture extends CompletableFuture implements Parcelable {
private static final boolean DEBUG = false;
private static final String LOG_TAG = AndroidFuture.class.getSimpleName();
private static final Executor DIRECT_EXECUTOR = Runnable::run;
private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];
private static @Nullable Handler sMainHandler;
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 = getMainHandler();
private final @Nullable IAndroidFuture mRemoteOrigin;
public AndroidFuture() {
super();
mRemoteOrigin = null;
}
AndroidFuture(Parcel in) {
super();
if (in.readBoolean()) {
// Done
if (in.readBoolean()) {
// Failed
completeExceptionally(readThrowable(in));
} else {
// Success
complete((T) in.readValue(null));
}
mRemoteOrigin = null;
} else {
// Not done
mRemoteOrigin = IAndroidFuture.Stub.asInterface(in.readStrongBinder());
}
}
@NonNull
private static Handler getMainHandler() {
// This isn't thread-safe but we are okay with it.
if (sMainHandler == null) {
sMainHandler = new Handler(Looper.getMainLooper());
}
return sMainHandler;
}
/**
* Create a completed future with the given value.
*
* @param value the value for the completed future
* @param the type of the value
* @return the completed future
*/
@NonNull
public static AndroidFuture completedFuture(U value) {
AndroidFuture future = new AndroidFuture<>();
future.complete(value);
return future;
}
@Override
public boolean complete(@Nullable T value) {
boolean changed = super.complete(value);
if (changed) {
onCompleted(value, null);
}
return changed;
}
@Override
public boolean completeExceptionally(@NonNull Throwable ex) {
boolean changed = super.completeExceptionally(ex);
if (changed) {
onCompleted(null, ex);
}
return changed;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
boolean changed = super.cancel(mayInterruptIfRunning);
if (changed) {
try {
get();
throw new IllegalStateException("Expected CancellationException");
} catch (CancellationException ex) {
onCompleted(null, ex);
} catch (Throwable e) {
throw new IllegalStateException("Expected CancellationException", e);
}
}
return changed;
}
@CallSuper
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;
mListener = null;
}
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
public AndroidFuture whenComplete(@NonNull BiConsumer super T, ? super Throwable> action) {
return whenCompleteAsync(action, DIRECT_EXECUTOR);
}
@Override
public AndroidFuture whenCompleteAsync(
@NonNull BiConsumer super T, ? super Throwable> action,
@NonNull Executor executor) {
Preconditions.checkNotNull(action);
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) -> {
callListener(oldListener, res, err);
callListener(action, res, err);
};
return this;
}
}
// isDone() == true at this point
T res = null;
Throwable err = null;
try {
res = get();
} catch (ExecutionException e) {
err = e.getCause();
} catch (Throwable e) {
err = e;
}
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(() -> callListener(listener, res, err));
}
}
/**
* Calls the provided listener, handling any exceptions that may arise.
*/
// package-private to avoid synthetic method when called from lambda
static void callListener(
@NonNull BiConsumer super TT, ? super Throwable> listener,
@Nullable TT res, @Nullable Throwable err) {
try {
try {
listener.accept(res, err);
} catch (Throwable t) {
if (err == null) {
// listener happy-case threw, but exception case might not throw, so report the
// same exception thrown by listener's happy-path to it again
listener.accept(null, t);
} else {
// listener exception-case threw
// give up on listener but preserve the original exception when throwing up
t.addSuppressed(err);
throw t;
}
}
} catch (Throwable t2) {
// give up on listener and log the result & exception to logcat
Log.e(LOG_TAG, "Failed to call whenComplete listener. res = " + res, t2);
}
}
/** @inheritDoc */
//@Override //TODO uncomment once java 9 APIs are exposed to frameworks
public AndroidFuture orTimeout(long timeout, @NonNull TimeUnit unit) {
mTimeoutHandler.postDelayed(this::triggerTimeout, this, unit.toMillis(timeout));
return this;
}
void triggerTimeout() {
cancelTimeout();
if (!isDone()) {
completeExceptionally(new TimeoutException());
}
}
/**
* Cancel all timeouts previously set with {@link #orTimeout}, if any.
*
* @return {@code this} for chaining
*/
public AndroidFuture cancelTimeout() {
mTimeoutHandler.removeCallbacksAndMessages(this);
return this;
}
/**
* Specifies the handler on which timeout is to be triggered
*/
public AndroidFuture setTimeoutHandler(@NonNull Handler h) {
cancelTimeout();
mTimeoutHandler = Preconditions.checkNotNull(h);
return this;
}
@Override
public AndroidFuture thenCompose(
@NonNull Function super T, ? extends CompletionStage> fn) {
return thenComposeAsync(fn, DIRECT_EXECUTOR);
}
@Override
public AndroidFuture thenComposeAsync(
@NonNull Function super T, ? extends CompletionStage> fn,
@NonNull Executor executor) {
return new ThenComposeAsync<>(this, fn, executor);
}
private static class ThenComposeAsync extends AndroidFuture
implements BiConsumer