RequestExecutor.java
/*
* Copyright 2021 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 androidx.core.provider;
import android.os.Handler;
import android.os.Process;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.core.util.Consumer;
import androidx.core.util.Preconditions;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Utility class that provides callback and wait operations for execute actions.
*/
class RequestExecutor {
private RequestExecutor() {}
/**
* Calls the callable, and returns the result as an argument to {@link Consumer#accept}.
*/
static <T> void execute(
@NonNull Executor executor,
@NonNull Callable<T> callable,
@NonNull Consumer<T> consumer
) {
// TODO check if this handler can be removed
// removing the callee handler cause breakage see aosp/1600057
final Handler calleeHandler = CalleeHandler.create();
executor.execute(new ReplyRunnable<>(calleeHandler, callable, consumer));
}
static <T> T submit(
@NonNull ExecutorService executor,
@NonNull final Callable<T> callable,
@IntRange(from = 0) int timeoutMillis
) throws InterruptedException {
Future<T> future = executor.submit(callable);
try {
return future.get(timeoutMillis, TimeUnit.MILLISECONDS);
} catch (ExecutionException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw e;
} catch (TimeoutException e) {
throw new InterruptedException("timeout");
}
}
static ThreadPoolExecutor createDefaultExecutor(
@NonNull String threadName,
int threadPriority,
@IntRange(from = 0) int keepAliveTimeInMillis
) {
ThreadFactory threadFactory = new DefaultThreadFactory(threadName, threadPriority);
// allow core thread timeout to timeout the core threads so that
// when no tasks arrive, the core threads can be killed
ThreadPoolExecutor executor = new ThreadPoolExecutor(
0 /* corePoolSize */,
1 /* maximumPoolSize */,
keepAliveTimeInMillis /* keepAliveTime */,
TimeUnit.MILLISECONDS /* keepAliveTime TimeUnit */,
new LinkedBlockingDeque<Runnable>() /* unbounded queue*/,
threadFactory
);
executor.allowCoreThreadTimeOut(true);
return executor;
}
static Executor createHandlerExecutor(@NonNull Handler handler) {
return new HandlerExecutor(handler);
}
/**
* An adapter {@link Executor} that posts all executed tasks onto the given {@link Handler}.
*
* The same as androidx.core.os.HandlerExecutor, however that class causes build failures.
* Adding this class temporarily until HandlerExecutor problem is solved.
*/
private static class HandlerExecutor implements Executor {
private final Handler mHandler;
HandlerExecutor(@NonNull Handler handler) {
mHandler = Preconditions.checkNotNull(handler);
}
@Override
public void execute(@NonNull Runnable command) {
if (!mHandler.post(Preconditions.checkNotNull(command))) {
throw new RejectedExecutionException(mHandler + " is shutting down");
}
}
}
/**
* Default Runnable to call the callable, and returns the result as an argument to
* {@link Consumer#accept}.
*/
private static class ReplyRunnable<T> implements Runnable {
private @NonNull Callable<T> mCallable;
private @NonNull Consumer<T> mConsumer;
private @NonNull Handler mHandler;
ReplyRunnable(
@NonNull Handler handler,
@NonNull Callable<T> callable,
@NonNull Consumer<T> consumer
) {
mCallable = callable;
mConsumer = consumer;
mHandler = handler;
}
@Override
public void run() {
T t;
try {
t = mCallable.call();
} catch (Exception e) {
t = null;
}
final T result = t;
final Consumer<T> consumer = mConsumer;
mHandler.post(new Runnable() {
@Override
public void run() {
consumer.accept(result);
}
});
}
}
private static class DefaultThreadFactory implements ThreadFactory {
private String mThreadName;
private int mPriority;
DefaultThreadFactory(@NonNull String threadName, int priority) {
mThreadName = threadName;
mPriority = priority;
}
@Override
public Thread newThread(Runnable runnable) {
return new ProcessPriorityThread(runnable, mThreadName, mPriority);
}
private static class ProcessPriorityThread extends Thread {
private final int mPriority;
ProcessPriorityThread(Runnable target, String name, int priority) {
super(target, name);
mPriority = priority;
}
@Override
public void run() {
Process.setThreadPriority(mPriority);
super.run();
}
}
}
}