// 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; import java.util.concurrent.atomic.AtomicLong; /** * 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. * *

See b/32831092

*

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. *

*/ public synchronized static ListeningExecutorService getSimReadExecutor() { if (sSimExecutor == null) { final ThreadPoolExecutor executor = new ThreadPoolExecutor( 1, 1, 30, TimeUnit.SECONDS, new LinkedBlockingQueue()); executor.allowCoreThreadTimeOut(true); sSimExecutor = MoreExecutors.listeningDecorator(executor); } return sSimExecutor; } /** * Wrapper around a handler that implements a subset of the ScheduledExecutorService * *

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. *

*/ 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 future = HandlerFuture .fromRunnable(mHandler, delay, unit, command); mHandler.postDelayed(future, unit.toMillis(delay)); return future; } @NonNull @Override public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { final HandlerFuture 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 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 extends ForwardingFuture implements RunnableScheduledFuture { private final Handler mHandler; private final long mDelayMillis; private final Callable mTask; private final SettableFuture mDelegate = SettableFuture.create(); private final AtomicLong mStart = new AtomicLong(-1); private HandlerFuture(Handler handler, long delay, TimeUnit timeUnit, Callable task) { mHandler = handler; mDelayMillis = timeUnit.toMillis(delay); mTask = task; } @Override public boolean isPeriodic() { return false; } @Override public long getDelay(TimeUnit unit) { long start = mStart.get(); if (start < 0) { return mDelayMillis; } long remaining = mDelayMillis - (System.currentTimeMillis() - start); return TimeUnit.MILLISECONDS.convert(remaining, unit); } @Override public int compareTo(Delayed o) { return Long.compare(getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS)); } @Override protected Future delegate() { return mDelegate; } @Override public boolean cancel(boolean b) { mHandler.removeCallbacks(this); return super.cancel(b); } @Override public void run() { if (!mStart.compareAndSet(-1, System.currentTimeMillis())) { // Already started return; } try { mDelegate.set(mTask.call()); } catch (Exception e) { mDelegate.setException(e); } } public static HandlerFuture fromRunnable(Handler handler, long delay, TimeUnit unit, final Runnable command) { return new HandlerFuture<>(handler, delay, unit, new Callable() { @Override public Void call() throws Exception { command.run(); return null; } }); } } }