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);
+            }
+        });
+    }
+}