1 // Copyright 2016 Google Inc. All Rights Reserved.
2 package com.android.contacts.util.concurrent;
3 
4 import android.os.AsyncTask;
5 import android.os.Handler;
6 import android.os.Looper;
7 import androidx.annotation.NonNull;
8 
9 import com.google.common.util.concurrent.ForwardingFuture;
10 import com.google.common.util.concurrent.Futures;
11 import com.google.common.util.concurrent.ListeningExecutorService;
12 import com.google.common.util.concurrent.MoreExecutors;
13 import com.google.common.util.concurrent.SettableFuture;
14 
15 import java.util.List;
16 import java.util.concurrent.AbstractExecutorService;
17 import java.util.concurrent.Callable;
18 import java.util.concurrent.Delayed;
19 import java.util.concurrent.ExecutorService;
20 import java.util.concurrent.Executors;
21 import java.util.concurrent.Future;
22 import java.util.concurrent.LinkedBlockingQueue;
23 import java.util.concurrent.RunnableScheduledFuture;
24 import java.util.concurrent.ScheduledExecutorService;
25 import java.util.concurrent.ScheduledFuture;
26 import java.util.concurrent.ThreadPoolExecutor;
27 import java.util.concurrent.TimeUnit;
28 import java.util.concurrent.atomic.AtomicLong;
29 
30 /**
31  * Provides some common executors for use with {@link Futures}
32  */
33 public class ContactsExecutors {
34 
ContactsExecutors()35     private ContactsExecutors() {}
36 
37     private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
38     private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
39 
40     // AsyncTask.THREAD_POOL_EXECUTOR is a ThreadPoolExecutor so we should end up always using that
41     // but we have a fallback in case the platform implementation changes in some future release.
42     private static final ListeningExecutorService DEFAULT_THREAD_POOL_EXECUTOR =
43             (AsyncTask.THREAD_POOL_EXECUTOR instanceof ExecutorService) ?
44                     MoreExecutors.listeningDecorator(
45                             (ExecutorService) AsyncTask.THREAD_POOL_EXECUTOR) :
46                     MoreExecutors.listeningDecorator(
47                             Executors.newFixedThreadPool(CORE_POOL_SIZE));
48 
49     // We initialize this lazily since in some cases we may never even read from the SIM card
50     private static ListeningExecutorService sSimExecutor;
51 
52     /**
53      * Returns the default thread pool that can be used for background work.
54      */
getDefaultThreadPoolExecutor()55     public static ListeningExecutorService getDefaultThreadPoolExecutor() {
56         return DEFAULT_THREAD_POOL_EXECUTOR;
57     }
58 
59     /**
60      * Creates an executor that runs commands on the application UI thread
61      */
newUiThreadExecutor()62     public static ScheduledExecutorService newUiThreadExecutor() {
63         return newHandlerExecutor(new Handler(Looper.getMainLooper()));
64     }
65 
66     /**
67      * Create an executor that posts commands to the provided handler
68      */
newHandlerExecutor(final Handler handler)69     public static ScheduledExecutorService newHandlerExecutor(final Handler handler) {
70         return new HandlerExecutorService(handler);
71     }
72 
73     /**
74      * Returns an ExecutorService that can be used to read from the SIM card.
75      *
76      * <p>See b/32831092</p>
77      * <p>A different executor than {@link ContactsExecutors#getDefaultThreadPoolExecutor()} is
78      * provided for this case because reads of the SIM card can block for long periods of time
79      * and if they do we might exhaust our thread pool. Additionally it appears that reading from
80      * the SIM provider from multiple threads concurrently can cause problems.
81      * </p>
82      */
getSimReadExecutor()83     public synchronized static ListeningExecutorService getSimReadExecutor() {
84         if (sSimExecutor == null) {
85             final ThreadPoolExecutor executor = new ThreadPoolExecutor(
86                     1, 1, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
87             executor.allowCoreThreadTimeOut(true);
88             sSimExecutor = MoreExecutors.listeningDecorator(executor);
89         }
90         return sSimExecutor;
91     }
92 
93     /**
94      * Wrapper around a handler that implements a subset of the ScheduledExecutorService
95      *
96      * <p>This class is useful for testability because Handler can't be mocked since it's
97      * methods are final. It might be better to just use Executors.newSingleThreadScheduledExecutor
98      * in the cases where we need to run some time based tasks.
99      * </p>
100      */
101     private static class HandlerExecutorService extends AbstractExecutorService
102             implements ScheduledExecutorService {
103         private final Handler mHandler;
104 
HandlerExecutorService(Handler handler)105         private HandlerExecutorService(Handler handler) {
106             mHandler = handler;
107         }
108 
109         @NonNull
110         @Override
schedule(final Runnable command, long delay, TimeUnit unit)111         public ScheduledFuture<?> schedule(final Runnable command, long delay, TimeUnit unit) {
112             final HandlerFuture<Void> future = HandlerFuture
113                     .fromRunnable(mHandler, delay, unit, command);
114             mHandler.postDelayed(future, unit.toMillis(delay));
115             return future;
116         }
117 
118         @NonNull
119         @Override
schedule(Callable<V> callable, long delay, TimeUnit unit)120         public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
121             final HandlerFuture<V> future = new HandlerFuture<>(mHandler, delay, unit, callable);
122             mHandler.postDelayed(future, unit.toMillis(delay));
123             return future;
124         }
125 
126         @NonNull
127         @Override
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)128         public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay,
129                 long period, TimeUnit unit) {
130             throw new UnsupportedOperationException();
131         }
132 
133         @NonNull
134         @Override
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)135         public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay,
136                 long delay, TimeUnit unit) {
137             throw new UnsupportedOperationException();
138         }
139 
140         @Override
shutdown()141         public void shutdown() {
142         }
143 
144         @Override
shutdownNow()145         public List<Runnable> shutdownNow() {
146             return null;
147         }
148 
149         @Override
isShutdown()150         public boolean isShutdown() {
151             return false;
152         }
153 
154         @Override
isTerminated()155         public boolean isTerminated() {
156             return false;
157         }
158 
159         @Override
awaitTermination(long timeout, TimeUnit unit)160         public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
161             throw new UnsupportedOperationException();
162         }
163 
164         @Override
execute(Runnable command)165         public void execute(Runnable command) {
166             mHandler.post(command);
167         }
168     }
169 
170     private static class HandlerFuture<T> extends ForwardingFuture<T> implements
171             RunnableScheduledFuture<T> {
172 
173         private final Handler mHandler;
174         private final long mDelayMillis;
175         private final Callable<T> mTask;
176         private final SettableFuture<T> mDelegate = SettableFuture.create();
177 
178         private final AtomicLong mStart = new AtomicLong(-1);
179 
HandlerFuture(Handler handler, long delay, TimeUnit timeUnit, Callable<T> task)180         private HandlerFuture(Handler handler, long delay, TimeUnit timeUnit, Callable<T> task) {
181             mHandler = handler;
182             mDelayMillis = timeUnit.toMillis(delay);
183             mTask = task;
184         }
185 
186         @Override
isPeriodic()187         public boolean isPeriodic() {
188             return false;
189         }
190 
191         @Override
getDelay(TimeUnit unit)192         public long getDelay(TimeUnit unit) {
193             long start = mStart.get();
194             if (start < 0) {
195                 return mDelayMillis;
196             }
197             long remaining = mDelayMillis - (System.currentTimeMillis() - start);
198             return TimeUnit.MILLISECONDS.convert(remaining, unit);
199         }
200 
201         @Override
compareTo(Delayed o)202         public int compareTo(Delayed o) {
203             return Long.compare(getDelay(TimeUnit.MILLISECONDS),
204                     o.getDelay(TimeUnit.MILLISECONDS));
205         }
206 
207         @Override
delegate()208         protected Future<T> delegate() {
209             return mDelegate;
210         }
211 
212         @Override
cancel(boolean b)213         public boolean cancel(boolean b) {
214             mHandler.removeCallbacks(this);
215             return super.cancel(b);
216         }
217 
218         @Override
run()219         public void run() {
220             if (!mStart.compareAndSet(-1, System.currentTimeMillis())) {
221                 // Already started
222                 return;
223             }
224             try {
225                 mDelegate.set(mTask.call());
226             } catch (Exception e) {
227                 mDelegate.setException(e);
228             }
229         }
230 
fromRunnable(Handler handler, long delay, TimeUnit unit, final Runnable command)231         public static HandlerFuture<Void> fromRunnable(Handler handler, long delay, TimeUnit unit,
232                 final Runnable command) {
233             return new HandlerFuture<>(handler, delay, unit, new Callable<Void>() {
234                 @Override
235                 public Void call() throws Exception {
236                     command.run();
237                     return null;
238                 }
239             });
240         }
241     }
242 }
243