Merge "Add executor utility classes" into ub-contactsdialer-i-dev
diff --git a/src/com/android/contacts/util/concurrent/ContactsExecutors.java b/src/com/android/contacts/util/concurrent/ContactsExecutors.java
new file mode 100644
index 0000000..bf18876
--- /dev/null
+++ b/src/com/android/contacts/util/concurrent/ContactsExecutors.java
@@ -0,0 +1,231 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+package com.android.contacts.util.concurrent;
+
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.annotation.NonNull;
+
+import com.google.common.util.concurrent.ForwardingFuture;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+
+import java.util.List;
+import java.util.concurrent.AbstractExecutorService;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RunnableScheduledFuture;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Provides some common executors for use with {@link Futures}
+ */
+public class ContactsExecutors {
+
+ private ContactsExecutors() {}
+
+ private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
+ private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
+
+ // AsyncTask.THREAD_POOL_EXECUTOR is a ThreadPoolExecutor so we should end up always using that
+ // but we have a fallback in case the platform implementation changes in some future release.
+ private static final ListeningExecutorService DEFAULT_THREAD_POOL_EXECUTOR =
+ (AsyncTask.THREAD_POOL_EXECUTOR instanceof ExecutorService) ?
+ MoreExecutors.listeningDecorator(
+ (ExecutorService) AsyncTask.THREAD_POOL_EXECUTOR) :
+ MoreExecutors.listeningDecorator(
+ Executors.newFixedThreadPool(CORE_POOL_SIZE));
+
+ // We initialize this lazily since in some cases we may never even read from the SIM card
+ private static ListeningExecutorService sSimExecutor;
+
+ /**
+ * Returns the default thread pool that can be used for background work.
+ */
+ public static ListeningExecutorService getDefaultThreadPoolExecutor() {
+ return DEFAULT_THREAD_POOL_EXECUTOR;
+ }
+
+ /**
+ * Creates an executor that runs commands on the application UI thread
+ */
+ public static ScheduledExecutorService newUiThreadExecutor() {
+ return newHandlerExecutor(new Handler(Looper.getMainLooper()));
+ }
+
+ /**
+ * Create an executor that posts commands to the provided handler
+ */
+ public static ScheduledExecutorService newHandlerExecutor(final Handler handler) {
+ return new HandlerExecutorService(handler);
+ }
+
+ /**
+ * Returns an ExecutorService that can be used to read from the SIM card.
+ *
+ * <p>See b/32831092</p>
+ * <p>A different executor than {@link ContactsExecutors#getDefaultThreadPoolExecutor()} is
+ * provided for this case because reads of the SIM card can block for long periods of time
+ * and if they do we might exhaust our thread pool. Additionally it appears that reading from
+ * the SIM provider from multiple threads concurrently can cause problems.
+ * </p>
+ */
+ public synchronized static ListeningExecutorService getSimReadExecutor() {
+ if (sSimExecutor == null) {
+ final ThreadPoolExecutor executor = new ThreadPoolExecutor(
+ 1, 1, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
+ executor.allowCoreThreadTimeOut(true);
+ sSimExecutor = MoreExecutors.listeningDecorator(executor);
+ }
+ return sSimExecutor;
+ }
+
+ /**
+ * Wrapper around a handler that implements a subset of the ScheduledExecutorService
+ *
+ * <p>This class is useful for testability because Handler can't be mocked since it's
+ * methods are final. It might be better to just use Executors.newSingleThreadScheduledExecutor
+ * in the cases where we need to run some time based tasks.
+ * </p>
+ */
+ private static class HandlerExecutorService extends AbstractExecutorService
+ implements ScheduledExecutorService {
+ private final Handler mHandler;
+
+ private HandlerExecutorService(Handler handler) {
+ mHandler = handler;
+ }
+
+ @NonNull
+ @Override
+ public ScheduledFuture<?> schedule(final Runnable command, long delay, TimeUnit unit) {
+ final HandlerFuture<Void> future = HandlerFuture
+ .fromRunnable(mHandler, delay, unit, command);
+ mHandler.postDelayed(future, unit.toMillis(delay));
+ return future;
+ }
+
+ @NonNull
+ @Override
+ public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+ final HandlerFuture<V> future = new HandlerFuture<>(mHandler, delay, unit, callable);
+ mHandler.postDelayed(future, unit.toMillis(delay));
+ return future;
+ }
+
+ @NonNull
+ @Override
+ public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay,
+ long period, TimeUnit unit) {
+ throw new UnsupportedOperationException();
+ }
+
+ @NonNull
+ @Override
+ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay,
+ long delay, TimeUnit unit) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void shutdown() {
+ }
+
+ @Override
+ public List<Runnable> shutdownNow() {
+ return null;
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return false;
+ }
+
+ @Override
+ public boolean isTerminated() {
+ return false;
+ }
+
+ @Override
+ public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ mHandler.post(command);
+ }
+ }
+
+ private static class HandlerFuture<T> extends ForwardingFuture<T> implements
+ RunnableScheduledFuture<T> {
+
+ private final Handler mHandler;
+ private final TimeUnit mUnit;
+ private final long mDelay;
+ private final Callable<T> mTask;
+ private final SettableFuture<T> mDelegate = SettableFuture.create();
+
+ private HandlerFuture(Handler handler, long delay, TimeUnit timeUnit, Callable<T> task) {
+ mHandler = handler;
+ mUnit = timeUnit;
+ mDelay = delay;
+ mTask = task;
+ }
+
+ @Override
+ public boolean isPeriodic() {
+ return false;
+ }
+
+ @Override
+ public long getDelay(TimeUnit unit) {
+ return unit.convert(mDelay, mUnit);
+ }
+
+ @Override
+ public int compareTo(Delayed o) {
+ return Long.compare(mDelay, o.getDelay(mUnit));
+ }
+
+ @Override
+ protected Future<T> delegate() {
+ return mDelegate;
+ }
+
+ @Override
+ public boolean cancel(boolean b) {
+ mHandler.removeCallbacks(this);
+ return super.cancel(b);
+ }
+
+ @Override
+ public void run() {
+ try {
+ mDelegate.set(mTask.call());
+ } catch (Exception e) {
+ mDelegate.setException(e);
+ }
+ }
+
+ public static HandlerFuture<Void> fromRunnable(Handler handler, long delay, TimeUnit unit,
+ final Runnable command) {
+ return new HandlerFuture<>(handler, delay, unit, new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ command.run();
+ return null;
+ }
+ });
+ }
+ }
+}
diff --git a/src/com/android/contacts/util/concurrent/FuturesUtil.java b/src/com/android/contacts/util/concurrent/FuturesUtil.java
new file mode 100644
index 0000000..113af93
--- /dev/null
+++ b/src/com/android/contacts/util/concurrent/FuturesUtil.java
@@ -0,0 +1,59 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+package com.android.contacts.util.concurrent;
+
+import android.os.Handler;
+
+import com.google.common.util.concurrent.FutureFallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Has utility methods for operating on ListenableFutures
+ */
+public class FuturesUtil {
+
+ /**
+ * See
+ * {@link FuturesUtil#withTimeout(ListenableFuture, long, TimeUnit, ScheduledExecutorService)}
+ */
+ public static <V> ListenableFuture<V> withTimeout(final ListenableFuture<V> future, long time,
+ TimeUnit unit, Handler handler) {
+ return withTimeout(future, time, unit, ContactsExecutors.newHandlerExecutor(handler));
+ }
+
+ /**
+ * Returns a future that completes with the result from the input future unless the specified
+ * time elapses before it finishes in which case the result will contain a TimeoutException and
+ * the input future will be canceled.
+ *
+ * <p>Guava has Futures.withTimeout but it isn't available until v19.0 and we depend on v14.0
+ * right now. Replace usages of this method if we upgrade our dependency.</p>
+ */
+ public static <V> ListenableFuture<V> withTimeout(final ListenableFuture<V> future, long time,
+ TimeUnit unit, ScheduledExecutorService executor) {
+ final AtomicBoolean didTimeout = new AtomicBoolean(false);
+ executor.schedule(new Runnable() {
+ @Override
+ public void run() {
+ didTimeout.set(!future.isDone() && !future.isCancelled());
+ future.cancel(true);
+ }
+ }, time, unit);
+
+ return Futures.withFallback(future, new FutureFallback<V>() {
+ @Override
+ public ListenableFuture<V> create(Throwable t) throws Exception {
+ if ((t instanceof CancellationException) && didTimeout.get()) {
+ return Futures.immediateFailedFuture(new TimeoutException("Timeout expired"));
+ }
+ return Futures.immediateFailedFuture(t);
+ }
+ });
+ }
+}