DO NOT MERGE Re-implement reading/writing Throwables from/to Parcel, without
Parcel private APIs. Bug:197228210 Test: atest CtsSecurityTestCases:android.security.cts.AndroidFutureTest (cherry picked from I577da5a3bc4ed537123b7eceaa5addf8f7bb0d92 and Icc5ce702f0cd84e9136dee3c65f63619df697358) Change-Id: I1d488c475f2f7af835a67496535cecdd6987c0cf
This commit is contained in:
@@ -16,23 +16,21 @@
|
|||||||
|
|
||||||
package com.android.internal.infra;
|
package com.android.internal.infra;
|
||||||
|
|
||||||
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
|
|
||||||
|
|
||||||
import android.annotation.CallSuper;
|
import android.annotation.CallSuper;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Message;
|
import android.os.Looper;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.util.ExceptionUtils;
|
import android.util.EventLog;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.GuardedBy;
|
||||||
import com.android.internal.util.Preconditions;
|
import com.android.internal.util.Preconditions;
|
||||||
import com.android.internal.util.function.pooled.PooledLambda;
|
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
import java.util.concurrent.CancellationException;
|
import java.util.concurrent.CancellationException;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.CompletionStage;
|
import java.util.concurrent.CompletionStage;
|
||||||
@@ -75,14 +73,16 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable
|
|||||||
|
|
||||||
private static final boolean DEBUG = false;
|
private static final boolean DEBUG = false;
|
||||||
private static final String LOG_TAG = AndroidFuture.class.getSimpleName();
|
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 final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];
|
||||||
|
private static @Nullable Handler sMainHandler;
|
||||||
|
|
||||||
private final @NonNull Object mLock = new Object();
|
private final @NonNull Object mLock = new Object();
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
private @Nullable BiConsumer<? super T, ? super Throwable> mListener;
|
private @Nullable BiConsumer<? super T, ? super Throwable> mListener;
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
private @Nullable Executor mListenerExecutor = DIRECT_EXECUTOR;
|
private @Nullable Executor mListenerExecutor = DIRECT_EXECUTOR;
|
||||||
private @NonNull Handler mTimeoutHandler = Handler.getMain();
|
private @NonNull Handler mTimeoutHandler = getMainHandler();
|
||||||
private final @Nullable IAndroidFuture mRemoteOrigin;
|
private final @Nullable IAndroidFuture mRemoteOrigin;
|
||||||
|
|
||||||
public AndroidFuture() {
|
public AndroidFuture() {
|
||||||
@@ -96,7 +96,7 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable
|
|||||||
// Done
|
// Done
|
||||||
if (in.readBoolean()) {
|
if (in.readBoolean()) {
|
||||||
// Failed
|
// Failed
|
||||||
completeExceptionally(unparcelException(in));
|
completeExceptionally(readThrowable(in));
|
||||||
} else {
|
} else {
|
||||||
// Success
|
// Success
|
||||||
complete((T) in.readValue(null));
|
complete((T) in.readValue(null));
|
||||||
@@ -108,6 +108,15 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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.
|
* Create a completed future with the given value.
|
||||||
*
|
*
|
||||||
@@ -236,9 +245,7 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable
|
|||||||
if (mListenerExecutor == DIRECT_EXECUTOR) {
|
if (mListenerExecutor == DIRECT_EXECUTOR) {
|
||||||
callListener(listener, res, err);
|
callListener(listener, res, err);
|
||||||
} else {
|
} else {
|
||||||
mListenerExecutor.execute(PooledLambda
|
mListenerExecutor.execute(() -> callListener(listener, res, err));
|
||||||
.obtainRunnable(AndroidFuture::callListener, listener, res, err)
|
|
||||||
.recycleOnUse());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,7 +267,8 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable
|
|||||||
} else {
|
} else {
|
||||||
// listener exception-case threw
|
// listener exception-case threw
|
||||||
// give up on listener but preserve the original exception when throwing up
|
// give up on listener but preserve the original exception when throwing up
|
||||||
throw ExceptionUtils.appendCause(t, err);
|
t.addSuppressed(err);
|
||||||
|
throw t;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable t2) {
|
} catch (Throwable t2) {
|
||||||
@@ -272,9 +280,7 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable
|
|||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
//@Override //TODO uncomment once java 9 APIs are exposed to frameworks
|
//@Override //TODO uncomment once java 9 APIs are exposed to frameworks
|
||||||
public AndroidFuture<T> orTimeout(long timeout, @NonNull TimeUnit unit) {
|
public AndroidFuture<T> orTimeout(long timeout, @NonNull TimeUnit unit) {
|
||||||
Message msg = PooledLambda.obtainMessage(AndroidFuture::triggerTimeout, this);
|
mTimeoutHandler.postDelayed(this::triggerTimeout, this, unit.toMillis(timeout));
|
||||||
msg.obj = this;
|
|
||||||
mTimeoutHandler.sendMessageDelayed(msg, unit.toMillis(timeout));
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -507,7 +513,7 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable
|
|||||||
result = get();
|
result = get();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
dest.writeBoolean(true);
|
dest.writeBoolean(true);
|
||||||
parcelException(dest, unwrapExecutionException(t));
|
writeThrowable(dest, unwrapExecutionException(t));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dest.writeBoolean(false);
|
dest.writeBoolean(false);
|
||||||
@@ -545,45 +551,75 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable
|
|||||||
* Alternative to {@link Parcel#writeException} that stores the stack trace, in a
|
* Alternative to {@link Parcel#writeException} that stores the stack trace, in a
|
||||||
* way consistent with the binder IPC exception propagation behavior.
|
* way consistent with the binder IPC exception propagation behavior.
|
||||||
*/
|
*/
|
||||||
private static void parcelException(Parcel p, @Nullable Throwable t) {
|
private static void writeThrowable(@NonNull Parcel parcel, @Nullable Throwable throwable) {
|
||||||
p.writeBoolean(t == null);
|
boolean hasThrowable = throwable != null;
|
||||||
if (t == null) {
|
parcel.writeBoolean(hasThrowable);
|
||||||
|
if (!hasThrowable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.writeInt(Parcel.getExceptionCode(t));
|
boolean isFrameworkParcelable = throwable instanceof Parcelable
|
||||||
p.writeString(t.getClass().getName());
|
&& throwable.getClass().getClassLoader() == Parcelable.class.getClassLoader();
|
||||||
p.writeString(t.getMessage());
|
parcel.writeBoolean(isFrameworkParcelable);
|
||||||
p.writeStackTrace(t);
|
if (isFrameworkParcelable) {
|
||||||
parcelException(p, t.getCause());
|
parcel.writeParcelable((Parcelable) throwable,
|
||||||
|
Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
parcel.writeString(throwable.getClass().getName());
|
||||||
|
parcel.writeString(throwable.getMessage());
|
||||||
|
StackTraceElement[] stackTrace = throwable.getStackTrace();
|
||||||
|
StringBuilder stackTraceBuilder = new StringBuilder();
|
||||||
|
int truncatedStackTraceLength = Math.min(stackTrace != null ? stackTrace.length : 0, 5);
|
||||||
|
for (int i = 0; i < truncatedStackTraceLength; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
stackTraceBuilder.append('\n');
|
||||||
|
}
|
||||||
|
stackTraceBuilder.append("\tat ").append(stackTrace[i]);
|
||||||
|
}
|
||||||
|
parcel.writeString(stackTraceBuilder.toString());
|
||||||
|
writeThrowable(parcel, throwable.getCause());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see #parcelException
|
* @see #writeThrowable
|
||||||
*/
|
*/
|
||||||
private static @Nullable Throwable unparcelException(Parcel p) {
|
private static @Nullable Throwable readThrowable(@NonNull Parcel parcel) {
|
||||||
if (p.readBoolean()) {
|
final boolean hasThrowable = parcel.readBoolean();
|
||||||
|
if (!hasThrowable) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
int exCode = p.readInt();
|
boolean isFrameworkParcelable = parcel.readBoolean();
|
||||||
String cls = p.readString();
|
if (isFrameworkParcelable) {
|
||||||
String msg = p.readString();
|
return parcel.readParcelable(Parcelable.class.getClassLoader());
|
||||||
String stackTrace = p.readInt() > 0 ? p.readString() : "\t<stack trace unavailable>";
|
|
||||||
msg += "\n" + stackTrace;
|
|
||||||
|
|
||||||
Exception ex = p.createExceptionOrNull(exCode, msg);
|
|
||||||
if (ex == null) {
|
|
||||||
ex = new RuntimeException(cls + ": " + msg);
|
|
||||||
}
|
}
|
||||||
ex.setStackTrace(EMPTY_STACK_TRACE);
|
|
||||||
|
|
||||||
Throwable cause = unparcelException(p);
|
String className = parcel.readString();
|
||||||
|
String message = parcel.readString();
|
||||||
|
String stackTrace = parcel.readString();
|
||||||
|
String messageWithStackTrace = message + '\n' + stackTrace;
|
||||||
|
Throwable throwable;
|
||||||
|
try {
|
||||||
|
Class<?> clazz = Class.forName(className, true, Parcelable.class.getClassLoader());
|
||||||
|
if (Throwable.class.isAssignableFrom(clazz)) {
|
||||||
|
Constructor<?> constructor = clazz.getConstructor(String.class);
|
||||||
|
throwable = (Throwable) constructor.newInstance(messageWithStackTrace);
|
||||||
|
} else {
|
||||||
|
android.util.EventLog.writeEvent(0x534e4554, "186530450", -1, "");
|
||||||
|
throwable = new RuntimeException(className + ": " + messageWithStackTrace);
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
throwable = new RuntimeException(className + ": " + messageWithStackTrace);
|
||||||
|
throwable.addSuppressed(t);
|
||||||
|
}
|
||||||
|
throwable.setStackTrace(EMPTY_STACK_TRACE);
|
||||||
|
Throwable cause = readThrowable(parcel);
|
||||||
if (cause != null) {
|
if (cause != null) {
|
||||||
ex.initCause(ex);
|
throwable.initCause(cause);
|
||||||
}
|
}
|
||||||
|
return throwable;
|
||||||
return ex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -26,14 +26,11 @@ import android.content.ServiceConnection;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.IInterface;
|
import android.os.IInterface;
|
||||||
|
import android.os.Looper;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.DebugUtils;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.android.internal.util.function.pooled.PooledLambda;
|
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -47,7 +44,6 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes care of managing a {@link ServiceConnection} and auto-disconnecting from the service upon
|
* Takes care of managing a {@link ServiceConnection} and auto-disconnecting from the service upon
|
||||||
* a certain timeout.
|
* a certain timeout.
|
||||||
@@ -220,6 +216,7 @@ public interface ServiceConnector<I extends IInterface> {
|
|||||||
private final @NonNull Queue<Job<I, ?>> mQueue = this;
|
private final @NonNull Queue<Job<I, ?>> mQueue = this;
|
||||||
private final @NonNull List<CompletionAwareJob<I, ?>> mUnfinishedJobs = new ArrayList<>();
|
private final @NonNull List<CompletionAwareJob<I, ?>> mUnfinishedJobs = new ArrayList<>();
|
||||||
|
|
||||||
|
private final @NonNull Handler mMainHandler = new Handler(Looper.getMainLooper());
|
||||||
private final @NonNull ServiceConnection mServiceConnection = this;
|
private final @NonNull ServiceConnection mServiceConnection = this;
|
||||||
private final @NonNull Runnable mTimeoutDisconnect = this;
|
private final @NonNull Runnable mTimeoutDisconnect = this;
|
||||||
|
|
||||||
@@ -250,9 +247,8 @@ public interface ServiceConnector<I extends IInterface> {
|
|||||||
* {@link IInterface}.
|
* {@link IInterface}.
|
||||||
* Typically this is {@code IMyInterface.Stub::asInterface}
|
* Typically this is {@code IMyInterface.Stub::asInterface}
|
||||||
*/
|
*/
|
||||||
public Impl(@NonNull Context context, @NonNull Intent intent,
|
public Impl(@NonNull Context context, @NonNull Intent intent, int bindingFlags,
|
||||||
@Context.BindServiceFlags int bindingFlags, @UserIdInt int userId,
|
@UserIdInt int userId, @Nullable Function<IBinder, I> binderAsInterface) {
|
||||||
@Nullable Function<IBinder, I> binderAsInterface) {
|
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mIntent = intent;
|
mIntent = intent;
|
||||||
mBindingFlags = bindingFlags;
|
mBindingFlags = bindingFlags;
|
||||||
@@ -264,7 +260,7 @@ public interface ServiceConnector<I extends IInterface> {
|
|||||||
* {@link Handler} on which {@link Job}s will be called
|
* {@link Handler} on which {@link Job}s will be called
|
||||||
*/
|
*/
|
||||||
protected Handler getJobHandler() {
|
protected Handler getJobHandler() {
|
||||||
return Handler.getMain();
|
return mMainHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -391,8 +387,7 @@ public interface ServiceConnector<I extends IInterface> {
|
|||||||
|
|
||||||
private boolean enqueue(@NonNull Job<I, ?> job) {
|
private boolean enqueue(@NonNull Job<I, ?> job) {
|
||||||
cancelTimeout();
|
cancelTimeout();
|
||||||
return getJobHandler().sendMessage(PooledLambda.obtainMessage(
|
return getJobHandler().post(() -> enqueueJobThread(job));
|
||||||
ServiceConnector.Impl::enqueueJobThread, this, job));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void enqueueJobThread(@NonNull Job<I, ?> job) {
|
void enqueueJobThread(@NonNull Job<I, ?> job) {
|
||||||
@@ -422,7 +417,7 @@ public interface ServiceConnector<I extends IInterface> {
|
|||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
logTrace();
|
logTrace();
|
||||||
}
|
}
|
||||||
Handler.getMain().removeCallbacks(mTimeoutDisconnect);
|
mMainHandler.removeCallbacks(mTimeoutDisconnect);
|
||||||
}
|
}
|
||||||
|
|
||||||
void completeExceptionally(@NonNull Job<?, ?> job, @NonNull Throwable ex) {
|
void completeExceptionally(@NonNull Job<?, ?> job, @NonNull Throwable ex) {
|
||||||
@@ -486,7 +481,7 @@ public interface ServiceConnector<I extends IInterface> {
|
|||||||
}
|
}
|
||||||
long timeout = getAutoDisconnectTimeoutMs();
|
long timeout = getAutoDisconnectTimeoutMs();
|
||||||
if (timeout > 0) {
|
if (timeout > 0) {
|
||||||
Handler.getMain().postDelayed(mTimeoutDisconnect, timeout);
|
mMainHandler.postDelayed(mTimeoutDisconnect, timeout);
|
||||||
} else if (DEBUG) {
|
} else if (DEBUG) {
|
||||||
Log.i(LOG_TAG, "Not scheduling unbind for permanently bound " + this);
|
Log.i(LOG_TAG, "Not scheduling unbind for permanently bound " + this);
|
||||||
}
|
}
|
||||||
@@ -502,7 +497,7 @@ public interface ServiceConnector<I extends IInterface> {
|
|||||||
logTrace();
|
logTrace();
|
||||||
}
|
}
|
||||||
mUnbinding = true;
|
mUnbinding = true;
|
||||||
getJobHandler().sendMessage(PooledLambda.obtainMessage(Impl::unbindJobThread, this));
|
getJobHandler().post(this::unbindJobThread);
|
||||||
}
|
}
|
||||||
|
|
||||||
void unbindJobThread() {
|
void unbindJobThread() {
|
||||||
@@ -659,10 +654,7 @@ public interface ServiceConnector<I extends IInterface> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void logTrace() {
|
private void logTrace() {
|
||||||
Log.i(LOG_TAG,
|
Log.i(LOG_TAG, "See stacktrace", new Throwable());
|
||||||
TextUtils.join(" -> ",
|
|
||||||
DebugUtils.callersWithin(ServiceConnector.class, /* offset= */ 1))
|
|
||||||
+ "(" + this + ")");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user