1cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher/*
2cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher * Copyright (C) 2014 The Android Open Source Project
3cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher *
4cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher * Licensed under the Apache License, Version 2.0 (the "License");
5cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher * you may not use this file except in compliance with the License.
6cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher * You may obtain a copy of the License at
7cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher *
8cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher *      http://www.apache.org/licenses/LICENSE-2.0
9cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher *
10cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher * Unless required by applicable law or agreed to in writing, software
11cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher * distributed under the License is distributed on an "AS IS" BASIS,
12cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher * See the License for the specific language governing permissions and
14cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher * limitations under the License.
15cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher */
16cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher
17cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucherpackage com.android.ex.camera2.portability;
18cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher
19cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucherimport android.os.Handler;
20cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucherimport android.os.HandlerThread;
21cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucherimport android.os.SystemClock;
22cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher
23cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucherimport com.android.ex.camera2.portability.debug.Log;
24cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher
25cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucherimport java.util.LinkedList;
26cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucherimport java.util.Queue;
27cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher
28a0842b40441db5332a5290f941021636b1182761Sol Boucherpublic class DispatchThread extends Thread {
29cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher    private static final Log.Tag TAG = new Log.Tag("DispatchThread");
30cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher    private static final long MAX_MESSAGE_QUEUE_LENGTH = 256;
31cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher
32cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher    private final Queue<Runnable> mJobQueue;
33cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher    private Boolean mIsEnded;
34cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher    private Handler mCameraHandler;
35cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher    private HandlerThread mCameraHandlerThread;
36cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher
37cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher    public DispatchThread(Handler cameraHandler, HandlerThread cameraHandlerThread) {
38cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher        super("Camera Job Dispatch Thread");
39cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher        mJobQueue = new LinkedList<Runnable>();
40cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher        mIsEnded = new Boolean(false);
41cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher        mCameraHandler = cameraHandler;
42cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher        mCameraHandlerThread = cameraHandlerThread;
43cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher    }
44cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher
45cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher    /**
46cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher     * Queues up the job.
47cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher     *
48cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher     * @param job The job to run.
49cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher     */
50cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher    public void runJob(Runnable job) {
51cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher        if (isEnded()) {
52cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher            throw new IllegalStateException(
53cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                    "Trying to run job on interrupted dispatcher thread");
54cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher        }
55cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher        synchronized (mJobQueue) {
56cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher            if (mJobQueue.size() == MAX_MESSAGE_QUEUE_LENGTH) {
57cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                throw new RuntimeException("Camera master thread job queue full");
58cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher            }
59cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher
60cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher            mJobQueue.add(job);
61cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher            mJobQueue.notifyAll();
62cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher        }
63cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher    }
64cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher
65cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher    /**
66cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher     * Queues up the job and wait for it to be done.
67cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher     *
68cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher     * @param job The job to run.
69cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher     * @param timeoutMs Timeout limit in milliseconds.
70cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher     * @param jobMsg The message to log when the job runs timeout.
71cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher     * @return Whether the job finishes before timeout.
72cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher     */
73cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher    public void runJobSync(final Runnable job, Object waitLock, long timeoutMs, String jobMsg) {
74cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher        String timeoutMsg = "Timeout waiting " + timeoutMs + "ms for " + jobMsg;
75cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher        synchronized (waitLock) {
76cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher            long timeoutBound = SystemClock.uptimeMillis() + timeoutMs;
77cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher            try {
78cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                runJob(job);
79cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                waitLock.wait(timeoutMs);
80cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                if (SystemClock.uptimeMillis() > timeoutBound) {
81cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                    throw new IllegalStateException(timeoutMsg);
82cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                }
83cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher            } catch (InterruptedException ex) {
84cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                if (SystemClock.uptimeMillis() > timeoutBound) {
85cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                    throw new IllegalStateException(timeoutMsg);
86cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                }
87cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher            }
88cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher        }
89cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher    }
90cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher
91cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher    /**
92cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher     * Gracefully ends this thread. Will stop after all jobs are processed.
93cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher     */
94cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher    public void end() {
95cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher        synchronized (mIsEnded) {
96cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher            mIsEnded = true;
97cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher        }
98cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher        synchronized(mJobQueue) {
99cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher            mJobQueue.notifyAll();
100cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher        }
101cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher    }
102cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher
103cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher    private boolean isEnded() {
104cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher        synchronized (mIsEnded) {
105cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher            return mIsEnded;
106cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher        }
107cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher    }
108cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher
109cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher    @Override
110cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher    public void run() {
111cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher        while(true) {
112cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher            Runnable job = null;
113cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher            synchronized (mJobQueue) {
114cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                while (mJobQueue.size() == 0 && !isEnded()) {
115cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                    try {
116cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                        mJobQueue.wait();
117cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                    } catch (InterruptedException ex) {
118cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                        Log.w(TAG, "Dispatcher thread wait() interrupted, exiting");
119cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                        break;
120cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                    }
121cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                }
122cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher
123cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                job = mJobQueue.poll();
124cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher            }
125cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher
126cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher            if (job == null) {
127cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                // mJobQueue.poll() returning null means wait() is
128cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                // interrupted and the queue is empty.
129cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                if (isEnded()) {
130cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                    break;
131cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                }
132cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                continue;
133cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher            }
134cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher
135cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher            job.run();
136cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher
137cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher            synchronized (DispatchThread.this) {
138cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                mCameraHandler.post(new Runnable() {
139cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                    @Override
140cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                    public void run() {
141cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                        synchronized (DispatchThread.this) {
142cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                            DispatchThread.this.notifyAll();
143cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                        }
144cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                    }
145cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                });
146cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                try {
147cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                    DispatchThread.this.wait();
148cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                } catch (InterruptedException ex) {
149cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                    // TODO: do something here.
150cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher                }
151cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher            }
152cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher        }
153cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher        mCameraHandlerThread.quitSafely();
154cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher    }
155cef46862d6937bc98bf1a6b087c5daa22b5239f3Sol Boucher}
156