1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.gallery3d.util;
18
19import java.util.concurrent.Executor;
20import java.util.concurrent.LinkedBlockingQueue;
21import java.util.concurrent.ThreadPoolExecutor;
22import java.util.concurrent.TimeUnit;
23
24public class ThreadPool {
25    private static final String TAG = "ThreadPool";
26    private static final int CORE_POOL_SIZE = 4;
27    private static final int MAX_POOL_SIZE = 8;
28    private static final int KEEP_ALIVE_TIME = 10; // 10 seconds
29
30    // Resource type
31    public static final int MODE_NONE = 0;
32    public static final int MODE_CPU = 1;
33    public static final int MODE_NETWORK = 2;
34
35    public static final JobContext JOB_CONTEXT_STUB = new JobContextStub();
36
37    ResourceCounter mCpuCounter = new ResourceCounter(2);
38    ResourceCounter mNetworkCounter = new ResourceCounter(2);
39
40    // A Job is like a Callable, but it has an addition JobContext parameter.
41    public interface Job<T> {
42        public T run(JobContext jc);
43    }
44
45    public interface JobContext {
46        boolean isCancelled();
47        void setCancelListener(CancelListener listener);
48        boolean setMode(int mode);
49    }
50
51    private static class JobContextStub implements JobContext {
52        @Override
53        public boolean isCancelled() {
54            return false;
55        }
56
57        @Override
58        public void setCancelListener(CancelListener listener) {
59        }
60
61        @Override
62        public boolean setMode(int mode) {
63            return true;
64        }
65    }
66
67    public interface CancelListener {
68        public void onCancel();
69    }
70
71    private static class ResourceCounter {
72        public int value;
73        public ResourceCounter(int v) {
74            value = v;
75        }
76    }
77
78    private final Executor mExecutor;
79
80    public ThreadPool() {
81        mExecutor = new ThreadPoolExecutor(
82                CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME,
83                TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
84                new PriorityThreadFactory("thread-pool",
85                android.os.Process.THREAD_PRIORITY_BACKGROUND));
86    }
87
88    // Submit a job to the thread pool. The listener will be called when the
89    // job is finished (or cancelled).
90    public <T> Future<T> submit(Job<T> job, FutureListener<T> listener) {
91        Worker<T> w = new Worker<T>(job, listener);
92        mExecutor.execute(w);
93        return w;
94    }
95
96    public <T> Future<T> submit(Job<T> job) {
97        return submit(job, null);
98    }
99
100    private class Worker<T> implements Runnable, Future<T>, JobContext {
101        private static final String TAG = "Worker";
102        private Job<T> mJob;
103        private FutureListener<T> mListener;
104        private CancelListener mCancelListener;
105        private ResourceCounter mWaitOnResource;
106        private volatile boolean mIsCancelled;
107        private boolean mIsDone;
108        private T mResult;
109        private int mMode;
110
111        public Worker(Job<T> job, FutureListener<T> listener) {
112            mJob = job;
113            mListener = listener;
114        }
115
116        // This is called by a thread in the thread pool.
117        public void run() {
118            T result = null;
119
120            // A job is in CPU mode by default. setMode returns false
121            // if the job is cancelled.
122            if (setMode(MODE_CPU)) {
123                try {
124                    result = mJob.run(this);
125                } catch (Throwable ex) {
126                    Log.w(TAG, "Exception in running a job", ex);
127                }
128            }
129
130            synchronized(this) {
131                setMode(MODE_NONE);
132                mResult = result;
133                mIsDone = true;
134                notifyAll();
135            }
136            if (mListener != null) mListener.onFutureDone(this);
137        }
138
139        // Below are the methods for Future.
140        public synchronized void cancel() {
141            if (mIsCancelled) return;
142            mIsCancelled = true;
143            if (mWaitOnResource != null) {
144                synchronized (mWaitOnResource) {
145                    mWaitOnResource.notifyAll();
146                }
147            }
148            if (mCancelListener != null) {
149                mCancelListener.onCancel();
150            }
151        }
152
153        public boolean isCancelled() {
154            return mIsCancelled;
155        }
156
157        public synchronized boolean isDone() {
158            return mIsDone;
159        }
160
161        public synchronized T get() {
162            while (!mIsDone) {
163                try {
164                    wait();
165                } catch (Exception ex) {
166                    Log.w(TAG, "ingore exception", ex);
167                    // ignore.
168                }
169            }
170            return mResult;
171        }
172
173        public void waitDone() {
174            get();
175        }
176
177        // Below are the methods for JobContext (only called from the
178        // thread running the job)
179        public synchronized void setCancelListener(CancelListener listener) {
180            mCancelListener = listener;
181            if (mIsCancelled && mCancelListener != null) {
182                mCancelListener.onCancel();
183            }
184        }
185
186        public boolean setMode(int mode) {
187            // Release old resource
188            ResourceCounter rc = modeToCounter(mMode);
189            if (rc != null) releaseResource(rc);
190            mMode = MODE_NONE;
191
192            // Acquire new resource
193            rc = modeToCounter(mode);
194            if (rc != null) {
195                if (!acquireResource(rc)) {
196                    return false;
197                }
198                mMode = mode;
199            }
200
201            return true;
202        }
203
204        private ResourceCounter modeToCounter(int mode) {
205            if (mode == MODE_CPU) {
206                return mCpuCounter;
207            } else if (mode == MODE_NETWORK) {
208                return mNetworkCounter;
209            } else {
210                return null;
211            }
212        }
213
214        private boolean acquireResource(ResourceCounter counter) {
215            while (true) {
216                synchronized (this) {
217                    if (mIsCancelled) {
218                        mWaitOnResource = null;
219                        return false;
220                    }
221                    mWaitOnResource = counter;
222                }
223
224                synchronized (counter) {
225                    if (counter.value > 0) {
226                        counter.value--;
227                        break;
228                    } else {
229                        try {
230                            counter.wait();
231                        } catch (InterruptedException ex) {
232                            // ignore.
233                        }
234                    }
235                }
236            }
237
238            synchronized (this) {
239                mWaitOnResource = null;
240            }
241
242            return true;
243        }
244
245        private void releaseResource(ResourceCounter counter) {
246            synchronized (counter) {
247                counter.value++;
248                counter.notifyAll();
249            }
250        }
251    }
252}
253