/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ex.camera2.portability; import android.os.Handler; import android.os.HandlerThread; import android.os.SystemClock; import com.android.ex.camera2.portability.debug.Log; import java.util.LinkedList; import java.util.Queue; public class DispatchThread extends Thread { private static final Log.Tag TAG = new Log.Tag("DispatchThread"); private static final long MAX_MESSAGE_QUEUE_LENGTH = 256; private final Queue mJobQueue; private Boolean mIsEnded; private Handler mCameraHandler; private HandlerThread mCameraHandlerThread; public DispatchThread(Handler cameraHandler, HandlerThread cameraHandlerThread) { super("Camera Job Dispatch Thread"); mJobQueue = new LinkedList(); mIsEnded = new Boolean(false); mCameraHandler = cameraHandler; mCameraHandlerThread = cameraHandlerThread; } /** * Queues up the job. * * @param job The job to run. */ public void runJob(Runnable job) { if (isEnded()) { throw new IllegalStateException( "Trying to run job on interrupted dispatcher thread"); } synchronized (mJobQueue) { if (mJobQueue.size() == MAX_MESSAGE_QUEUE_LENGTH) { throw new RuntimeException("Camera master thread job queue full"); } mJobQueue.add(job); mJobQueue.notifyAll(); } } /** * Queues up the job and wait for it to be done. * * @param job The job to run. * @param timeoutMs Timeout limit in milliseconds. * @param jobMsg The message to log when the job runs timeout. * @return Whether the job finishes before timeout. */ public void runJobSync(final Runnable job, Object waitLock, long timeoutMs, String jobMsg) { String timeoutMsg = "Timeout waiting " + timeoutMs + "ms for " + jobMsg; synchronized (waitLock) { long timeoutBound = SystemClock.uptimeMillis() + timeoutMs; try { runJob(job); waitLock.wait(timeoutMs); if (SystemClock.uptimeMillis() > timeoutBound) { throw new IllegalStateException(timeoutMsg); } } catch (InterruptedException ex) { if (SystemClock.uptimeMillis() > timeoutBound) { throw new IllegalStateException(timeoutMsg); } } } } /** * Gracefully ends this thread. Will stop after all jobs are processed. */ public void end() { synchronized (mIsEnded) { mIsEnded = true; } synchronized(mJobQueue) { mJobQueue.notifyAll(); } } private boolean isEnded() { synchronized (mIsEnded) { return mIsEnded; } } @Override public void run() { while(true) { Runnable job = null; synchronized (mJobQueue) { while (mJobQueue.size() == 0 && !isEnded()) { try { mJobQueue.wait(); } catch (InterruptedException ex) { Log.w(TAG, "Dispatcher thread wait() interrupted, exiting"); break; } } job = mJobQueue.poll(); } if (job == null) { // mJobQueue.poll() returning null means wait() is // interrupted and the queue is empty. if (isEnded()) { break; } continue; } job.run(); synchronized (DispatchThread.this) { mCameraHandler.post(new Runnable() { @Override public void run() { synchronized (DispatchThread.this) { DispatchThread.this.notifyAll(); } } }); try { DispatchThread.this.wait(); } catch (InterruptedException ex) { // TODO: do something here. } } } mCameraHandlerThread.quitSafely(); } }