1 /*
2  * Copyright (C) 2014 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 
17 package com.android.ex.camera2.portability;
18 
19 import android.os.Handler;
20 import android.os.HandlerThread;
21 import android.os.SystemClock;
22 
23 import com.android.ex.camera2.portability.debug.Log;
24 
25 import java.util.LinkedList;
26 import java.util.Queue;
27 
28 public class DispatchThread extends Thread {
29     private static final Log.Tag TAG = new Log.Tag("DispatchThread");
30     private static final long MAX_MESSAGE_QUEUE_LENGTH = 256;
31 
32     private final Queue<Runnable> mJobQueue;
33     private Boolean mIsEnded;
34     private Handler mCameraHandler;
35     private HandlerThread mCameraHandlerThread;
36 
DispatchThread(Handler cameraHandler, HandlerThread cameraHandlerThread)37     public DispatchThread(Handler cameraHandler, HandlerThread cameraHandlerThread) {
38         super("Camera Job Dispatch Thread");
39         mJobQueue = new LinkedList<Runnable>();
40         mIsEnded = new Boolean(false);
41         mCameraHandler = cameraHandler;
42         mCameraHandlerThread = cameraHandlerThread;
43     }
44 
45     /**
46      * Queues up the job.
47      *
48      * @param job The job to run.
49      */
runJob(Runnable job)50     public void runJob(Runnable job) {
51         if (isEnded()) {
52             throw new IllegalStateException(
53                     "Trying to run job on interrupted dispatcher thread");
54         }
55         synchronized (mJobQueue) {
56             if (mJobQueue.size() == MAX_MESSAGE_QUEUE_LENGTH) {
57                 throw new RuntimeException("Camera master thread job queue full");
58             }
59 
60             mJobQueue.add(job);
61             mJobQueue.notifyAll();
62         }
63     }
64 
65     /**
66      * Queues up the job and wait for it to be done.
67      *
68      * @param job The job to run.
69      * @param timeoutMs Timeout limit in milliseconds.
70      * @param jobMsg The message to log when the job runs timeout.
71      * @return Whether the job finishes before timeout.
72      */
runJobSync(final Runnable job, Object waitLock, long timeoutMs, String jobMsg)73     public void runJobSync(final Runnable job, Object waitLock, long timeoutMs, String jobMsg) {
74         String timeoutMsg = "Timeout waiting " + timeoutMs + "ms for " + jobMsg;
75         synchronized (waitLock) {
76             long timeoutBound = SystemClock.uptimeMillis() + timeoutMs;
77             try {
78                 runJob(job);
79                 waitLock.wait(timeoutMs);
80                 if (SystemClock.uptimeMillis() > timeoutBound) {
81                     throw new IllegalStateException(timeoutMsg);
82                 }
83             } catch (InterruptedException ex) {
84                 if (SystemClock.uptimeMillis() > timeoutBound) {
85                     throw new IllegalStateException(timeoutMsg);
86                 }
87             }
88         }
89     }
90 
91     /**
92      * Gracefully ends this thread. Will stop after all jobs are processed.
93      */
end()94     public void end() {
95         synchronized (mIsEnded) {
96             mIsEnded = true;
97         }
98         synchronized(mJobQueue) {
99             mJobQueue.notifyAll();
100         }
101     }
102 
isEnded()103     private boolean isEnded() {
104         synchronized (mIsEnded) {
105             return mIsEnded;
106         }
107     }
108 
109     @Override
run()110     public void run() {
111         while(true) {
112             Runnable job = null;
113             synchronized (mJobQueue) {
114                 while (mJobQueue.size() == 0 && !isEnded()) {
115                     try {
116                         mJobQueue.wait();
117                     } catch (InterruptedException ex) {
118                         Log.w(TAG, "Dispatcher thread wait() interrupted, exiting");
119                         break;
120                     }
121                 }
122 
123                 job = mJobQueue.poll();
124             }
125 
126             if (job == null) {
127                 // mJobQueue.poll() returning null means wait() is
128                 // interrupted and the queue is empty.
129                 if (isEnded()) {
130                     break;
131                 }
132                 continue;
133             }
134 
135             job.run();
136 
137             synchronized (DispatchThread.this) {
138                 mCameraHandler.post(new Runnable() {
139                     @Override
140                     public void run() {
141                         synchronized (DispatchThread.this) {
142                             DispatchThread.this.notifyAll();
143                         }
144                     }
145                 });
146                 try {
147                     DispatchThread.this.wait();
148                 } catch (InterruptedException ex) {
149                     // TODO: do something here.
150                 }
151             }
152         }
153         mCameraHandlerThread.quitSafely();
154     }
155 }
156