273 lines
8.8 KiB
Java
273 lines
8.8 KiB
Java
/*
|
|
* Copyright (C) 2016 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.modules.utils;
|
|
|
|
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.os.Handler;
|
|
import android.os.Parcel;
|
|
import android.os.Parcelable;
|
|
import android.os.RemoteException;
|
|
import android.os.SystemClock;
|
|
import android.util.Log;
|
|
|
|
import com.android.internal.annotations.GuardedBy;
|
|
|
|
import java.io.Serializable;
|
|
import java.time.Duration;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
import java.util.concurrent.ExecutionException;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.TimeoutException;
|
|
|
|
/**
|
|
* Generic interface for receiving a callback result from someone.
|
|
* Allow the server end to synchronously wait on the response from the client.
|
|
* This enables an RPC like system but with the ability to timeout and discard late results.
|
|
*
|
|
* <p>NOTE: Use the static {@link #get} method to retrieve an available instance of this class.
|
|
* If no instances are available, a new one is created.
|
|
*/
|
|
public final class SynchronousResultReceiver<T> implements Parcelable {
|
|
private static final String TAG = "SynchronousResultReceiver";
|
|
private final boolean mLocal;
|
|
private boolean mIsCompleted;
|
|
private final static Object sLock = new Object();
|
|
private final static int QUEUE_THRESHOLD = 4;
|
|
|
|
@GuardedBy("sLock")
|
|
private CompletableFuture<Result<T>> mFuture = new CompletableFuture<>();
|
|
|
|
@GuardedBy("sLock")
|
|
private static final ConcurrentLinkedQueue<SynchronousResultReceiver> sAvailableReceivers
|
|
= new ConcurrentLinkedQueue<>();
|
|
|
|
public static <T> SynchronousResultReceiver<T> get() {
|
|
synchronized(sLock) {
|
|
if (sAvailableReceivers.isEmpty()) {
|
|
return new SynchronousResultReceiver();
|
|
}
|
|
SynchronousResultReceiver receiver = sAvailableReceivers.poll();
|
|
receiver.resetLocked();
|
|
return receiver;
|
|
}
|
|
}
|
|
|
|
private SynchronousResultReceiver() {
|
|
mLocal = true;
|
|
mIsCompleted = false;
|
|
}
|
|
|
|
@GuardedBy("sLock")
|
|
private void releaseLocked() {
|
|
mFuture = null;
|
|
if (sAvailableReceivers.size() < QUEUE_THRESHOLD) {
|
|
sAvailableReceivers.add(this);
|
|
}
|
|
}
|
|
|
|
@GuardedBy("sLock")
|
|
private void resetLocked() {
|
|
mFuture = new CompletableFuture<>();
|
|
mIsCompleted = false;
|
|
}
|
|
|
|
private CompletableFuture<Result<T>> getFuture() {
|
|
synchronized (sLock) {
|
|
return mFuture;
|
|
}
|
|
}
|
|
|
|
public static class Result<T> implements Parcelable {
|
|
private final @Nullable T mObject;
|
|
private final RuntimeException mException;
|
|
|
|
public Result(RuntimeException exception) {
|
|
mObject = null;
|
|
mException = exception;
|
|
}
|
|
|
|
public Result(@Nullable T object) {
|
|
mObject = object;
|
|
mException = null;
|
|
}
|
|
|
|
/**
|
|
* Return the stored value
|
|
* May throw a {@link RuntimeException} thrown from the client
|
|
*/
|
|
public T getValue(T defaultValue) {
|
|
if (mException != null) {
|
|
throw mException;
|
|
}
|
|
if (mObject == null) {
|
|
return defaultValue;
|
|
}
|
|
return mObject;
|
|
}
|
|
|
|
public int describeContents() {
|
|
return 0;
|
|
}
|
|
|
|
public void writeToParcel(@NonNull Parcel out, int flags) {
|
|
out.writeValue(mObject);
|
|
out.writeValue(mException);
|
|
}
|
|
|
|
private Result(Parcel in) {
|
|
mObject = (T)in.readValue(null);
|
|
mException= (RuntimeException)in.readValue(null);
|
|
}
|
|
|
|
public static final @NonNull Parcelable.Creator<Result<?>> CREATOR =
|
|
new Parcelable.Creator<Result<?>>() {
|
|
public Result createFromParcel(Parcel in) {
|
|
return new Result(in);
|
|
}
|
|
public Result[] newArray(int size) {
|
|
return new Result[size];
|
|
}
|
|
};
|
|
}
|
|
|
|
private void complete(Result<T> result) {
|
|
if (mIsCompleted) {
|
|
throw new IllegalStateException("Receiver has already been completed");
|
|
}
|
|
mIsCompleted = true;
|
|
if (mLocal) {
|
|
getFuture().complete(result);
|
|
} else {
|
|
final ISynchronousResultReceiver rr;
|
|
synchronized (this) {
|
|
rr = mReceiver;
|
|
}
|
|
if (rr != null) {
|
|
try {
|
|
rr.send(result);
|
|
} catch (RemoteException e) {
|
|
Log.w(TAG, "Failed to complete future");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deliver a result to this receiver.
|
|
*
|
|
* @param resultData Additional data provided by you.
|
|
*/
|
|
public void send(@Nullable T resultData) {
|
|
complete(new Result<>(resultData));
|
|
}
|
|
|
|
/**
|
|
* Deliver an {@link Exception} to this receiver
|
|
*
|
|
* @param e exception to be sent
|
|
*/
|
|
public void propagateException(@NonNull RuntimeException e) {
|
|
Objects.requireNonNull(e, "RuntimeException cannot be null");
|
|
complete(new Result<>(e));
|
|
}
|
|
|
|
/**
|
|
* Blocks waiting for the result from the remote client.
|
|
*
|
|
* If it is interrupted before completion of the duration, wait again with remaining time until
|
|
* the deadline.
|
|
*
|
|
* @param timeout The duration to wait before sending a {@link TimeoutException}
|
|
* @return the Result
|
|
* @throws TimeoutException if the timeout in milliseconds expired.
|
|
*/
|
|
public @NonNull Result<T> awaitResultNoInterrupt(@NonNull Duration timeout)
|
|
throws TimeoutException {
|
|
Objects.requireNonNull(timeout, "Null timeout is not allowed");
|
|
|
|
final long startWaitNanoTime = SystemClock.elapsedRealtimeNanos();
|
|
Duration remainingTime = timeout;
|
|
while (!remainingTime.isNegative()) {
|
|
try {
|
|
Result<T> result = getFuture().get(remainingTime.toMillis(), TimeUnit.MILLISECONDS);
|
|
synchronized (sLock) {
|
|
releaseLocked();
|
|
return result;
|
|
}
|
|
} catch (ExecutionException e) {
|
|
// This will NEVER happen.
|
|
throw new AssertionError("Error receiving response", e);
|
|
} catch (InterruptedException e) {
|
|
// The thread was interrupted, try and get the value again, this time
|
|
// with the remaining time until the deadline.
|
|
remainingTime = timeout.minus(
|
|
Duration.ofNanos(SystemClock.elapsedRealtimeNanos() - startWaitNanoTime));
|
|
}
|
|
}
|
|
synchronized (sLock) {
|
|
releaseLocked();
|
|
}
|
|
throw new TimeoutException();
|
|
}
|
|
|
|
ISynchronousResultReceiver mReceiver = null;
|
|
|
|
private final class MyResultReceiver extends ISynchronousResultReceiver.Stub {
|
|
public void send(@SuppressWarnings("rawtypes") @NonNull Result result) {
|
|
@SuppressWarnings("unchecked") Result<T> res = (Result<T>) result;
|
|
CompletableFuture<Result<T>> future;
|
|
future = getFuture();
|
|
if (future != null) {
|
|
future.complete(res);
|
|
}
|
|
}
|
|
}
|
|
|
|
public int describeContents() {
|
|
return 0;
|
|
}
|
|
|
|
public void writeToParcel(@NonNull Parcel out, int flags) {
|
|
synchronized (this) {
|
|
if (mReceiver == null) {
|
|
mReceiver = new MyResultReceiver();
|
|
}
|
|
out.writeStrongBinder(mReceiver.asBinder());
|
|
}
|
|
}
|
|
|
|
private SynchronousResultReceiver(Parcel in) {
|
|
mLocal = false;
|
|
mIsCompleted = false;
|
|
mReceiver = ISynchronousResultReceiver.Stub.asInterface(in.readStrongBinder());
|
|
}
|
|
|
|
public static final @NonNull Parcelable.Creator<SynchronousResultReceiver<?>> CREATOR =
|
|
new Parcelable.Creator<SynchronousResultReceiver<?>>() {
|
|
public SynchronousResultReceiver<?> createFromParcel(Parcel in) {
|
|
return new SynchronousResultReceiver(in);
|
|
}
|
|
public SynchronousResultReceiver<?>[] newArray(int size) {
|
|
return new SynchronousResultReceiver[size];
|
|
}
|
|
};
|
|
}
|