2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.camera.processing.imagebackend;
19 import android.os.Process;
21 import com.android.camera.async.AndroidPriorityThread;
22 import com.android.camera.debug.Log;
23 import com.android.camera.processing.ProcessingTaskConsumer;
24 import com.android.camera.processing.memory.ByteBufferDirectPool;
25 import com.android.camera.processing.memory.LruResourcePool;
26 import com.android.camera.session.CaptureSession;
27 import com.android.camera.util.Size;
28 import com.google.common.base.Optional;
30 import java.nio.ByteBuffer;
31 import java.util.HashMap;
32 import java.util.HashSet;
35 import java.util.concurrent.Executor;
36 import java.util.concurrent.ExecutorService;
37 import java.util.concurrent.Executors;
38 import java.util.concurrent.ThreadFactory;
39 import java.util.concurrent.locks.Condition;
40 import java.util.concurrent.locks.ReentrantLock;
43 * This ImageBackend is created for the purpose of creating a task-running
44 * infrastructure that has two-level of priority and doing the book-keeping to
45 * keep track of tasks that use Android Images. Android.media.images are
46 * critical system resources that MUST be properly managed in order to maintain
47 * camera application performance. Android.media.images are merely Java handles
48 * to regions of physically contiguous memory used by the camera hardware as a
49 * destination for imaging data. In general, this physically contiguous memory
50 * is not counted as an application resource, but as a system resources held by
51 * the application and does NOT count against the limits of application memory.
52 * The performance pressures of both computing and memory resources must often
53 * be prioritized in releasing Android.media.images in a timely manner. In order
54 * to properly balance these concerns, most image processing requested should be
55 * routed through this object. This object is also responsible for releasing
56 * Android.media image as soon as possible, so as not to stall the camera
57 * hardware subsystem. Image that reserve these images are a subclass of the
58 * basic Java Runnable with a few conditions placed upon their run()
61 * <li>The task will try to release the image as early as possible by calling
62 * the releaseSemaphoreReference as soon as a reference to the original image is
63 * no longer required.</li>
64 * <li>A set of tasks that require ImageData must only happen on the first
65 * receiveImage call. receiveImage must only be called once per image.</li>
66 * <li>However, the submitted tasks may spawn new tasks via the appendTask with
67 * any image that have had a task submitted, but NOT released via
68 * releaseSemaphoreReference.</li>
69 * <li>Computation that is dependent on multiple images should be written into
70 * this task framework in a distributed manner where image task can be computed
71 * independently and join their results to a common shared object.This style of
72 * implementation allows for the earliest release of Android Images while
73 * honoring the resources priorities set by this class. See the Lucky shot
74 * implementation for a concrete example for this shared object and its
75 * respective task {@link TaskLuckyShotSession} {@link LuckyShotSession}</li>
77 * To integrate with the ProcessingServiceManager, ImageBackend also signals to
78 * the ProcessingServiceManager its processing state by enqueuing
79 * ImageShadowTasks on each ImageBackend::receiveImage call. These ImageShadow
80 * tasks have no implementation, but emulate the processing delay by blocking
81 * until all tasks submitted and spawned by a particular receiveImage call have
82 * completed their processing. This emulated functionality ensures that other
83 * ProcessingTasks associated with Lens Blur and Panorama are not processing
84 * while the ImageBackend is running. Unfairly, the ImageBackend proceeds with
85 * its own processing regardless of the state of ImageShadowTask.
86 * ImageShadowTasks that are associated with ImageBackend tasks that have
87 * already been completed should return immediately on its process call.
89 public class ImageBackend implements ImageConsumer, ImageTaskManager {
90 private static final Log.Tag TAG = new Log.Tag("ImageBackend");
92 protected static final int NUM_THREADS_FAST = 2;
93 protected static final int NUM_THREADS_AVERAGE = 2;
94 protected static final int NUM_THREADS_SLOW = 2;
96 private static final int FAST_THREAD_PRIORITY = Process.THREAD_PRIORITY_DISPLAY;
97 private static final int AVERAGE_THREAD_PRIORITY = Process.THREAD_PRIORITY_DEFAULT
98 + Process.THREAD_PRIORITY_LESS_FAVORABLE;
99 private static final int SLOW_THREAD_PRIORITY = Process.THREAD_PRIORITY_BACKGROUND
100 + Process.THREAD_PRIORITY_MORE_FAVORABLE;
102 private static final int IMAGE_BACKEND_HARD_REF_POOL_SIZE = 2;
104 protected final ProcessingTaskConsumer mProcessingTaskConsumer;
107 * Map for TaskImageContainer and the release of ImageProxy Book-keeping
109 protected final Map<ImageToProcess, ImageReleaseProtocol> mImageSemaphoreMap;
111 * Map for ImageShadowTask and release of blocking on
112 * ImageShadowTask::process
114 protected final Map<CaptureSession, ImageShadowTask> mShadowTaskMap;
116 // The available threadpools for scheduling
117 protected final ExecutorService mThreadPoolFast;
118 protected final ExecutorService mThreadPoolAverage;
119 protected final ExecutorService mThreadPoolSlow;
121 private final LruResourcePool<Integer, ByteBuffer> mByteBufferDirectPool;
124 * Approximate viewable size (in pixels) for the fast thumbnail in the
125 * current UX definition of the product. Note that these values will be the
126 * minimum size of FAST_THUMBNAIL target for the CONVERT_TO_RGB_PREVIEW
129 private final Size mTinyThumbnailTargetSize;
132 * A standard viewable size (in pixels) for the filmstrip thumbnail in the
133 * current UX definition of the product. Note that this size is the minimum
134 * size for the Preview on the filmstrip associated with
135 * COMPRESS_TO_JPEG_AND_WRITE_TO_DISK task.
137 private final static Size FILMSTRIP_THUMBNAIL_TARGET_SIZE = new Size(512, 384);
139 // Some invariants to know that we're keeping track of everything
140 // that reflect the state of mImageSemaphoreMap
141 private int mOutstandingImageRefs = 0;
143 private int mOutstandingImageOpened = 0;
145 private int mOutstandingImageClosed = 0;
147 // Objects that may be registered to this objects events.
148 private ImageProcessorProxyListener mProxyListener = null;
150 // Default constructor, values are conservatively targeted to the Nexus 6
151 public ImageBackend(ProcessingTaskConsumer processingTaskConsumer, int tinyThumbnailSize) {
152 mThreadPoolFast = Executors.newFixedThreadPool(NUM_THREADS_FAST, new FastThreadFactory());
153 mThreadPoolAverage = Executors.newFixedThreadPool(NUM_THREADS_AVERAGE,
154 new AverageThreadFactory());
155 mThreadPoolSlow = Executors.newFixedThreadPool(NUM_THREADS_SLOW, new SlowThreadFactory());
156 mByteBufferDirectPool = new ByteBufferDirectPool(IMAGE_BACKEND_HARD_REF_POOL_SIZE);
157 mProxyListener = new ImageProcessorProxyListener();
158 mImageSemaphoreMap = new HashMap<>();
159 mShadowTaskMap = new HashMap<>();
160 mProcessingTaskConsumer = processingTaskConsumer;
161 mTinyThumbnailTargetSize = new Size(tinyThumbnailSize, tinyThumbnailSize);
165 * Direct Injection Constructor for Testing purposes.
167 * @param fastService Service where Tasks of FAST Priority are placed.
168 * @param averageService Service where Tasks of AVERAGE Priority are placed.
169 * @param slowService Service where Tasks of SLOW Priority are placed.
170 * @param imageProcessorProxyListener iamge proxy listener to be used
172 public ImageBackend(ExecutorService fastService,
173 ExecutorService averageService,
174 ExecutorService slowService,
175 LruResourcePool<Integer, ByteBuffer> byteBufferDirectPool,
176 ImageProcessorProxyListener imageProcessorProxyListener,
177 ProcessingTaskConsumer processingTaskConsumer,
178 int tinyThumbnailSize) {
179 mThreadPoolFast = fastService;
180 mThreadPoolAverage = averageService;
181 mThreadPoolSlow = slowService;
182 mByteBufferDirectPool = byteBufferDirectPool;
183 mProxyListener = imageProcessorProxyListener;
184 mImageSemaphoreMap = new HashMap<>();
185 mShadowTaskMap = new HashMap<>();
186 mProcessingTaskConsumer = processingTaskConsumer;
187 mTinyThumbnailTargetSize = new Size(tinyThumbnailSize, tinyThumbnailSize);
191 * Simple getter for the associated listener object associated with this
192 * instantiation that handles registration of events listeners.
194 * @return listener proxy that handles events messaging for this object.
196 public ImageProcessorProxyListener getProxyListener() {
197 return mProxyListener;
201 * Wrapper function for all log messages created by this object. Default
202 * implementation is to send messages to the Android logger. For test
203 * purposes, this method can be overridden to avoid "Stub!" Runtime
204 * exceptions in Unit Tests.
206 public void logWrapper(String message) {
211 * @return Number of Image references currently held by this instance
214 public int getNumberOfReservedOpenImages() {
215 synchronized (mImageSemaphoreMap) {
216 // since mOutstandingImageOpened, mOutstandingImageClosed reflect
217 // the historical state of mImageSemaphoreMap, we need to lock on
218 // before we return a value.
219 return mOutstandingImageOpened - mOutstandingImageClosed;
224 * Returns of the number of receiveImage calls that are currently enqueued
225 * and/or being processed.
227 * @return The number of receiveImage calls that are currently enqueued
228 * and/or being processed
231 public int getNumberOfOutstandingCalls() {
232 synchronized (mShadowTaskMap) {
233 return mShadowTaskMap.size();
238 * Signals the ImageBackend that a tasks has released a reference to the
239 * image. Imagebackend determines whether all references have been released
240 * and applies its specified release protocol of closing image and/or
241 * unblocking the caller. Should ONLY be called by the tasks running on this
244 * @param img the image to be released by the task.
245 * @param executor the executor on which the image close is run. if null,
246 * image close is run by the calling thread (usually the main
250 public void releaseSemaphoreReference(final ImageToProcess img, Executor executor) {
251 synchronized (mImageSemaphoreMap) {
252 ImageReleaseProtocol protocol = mImageSemaphoreMap.get(img);
253 if (protocol == null || protocol.getCount() <= 0) {
254 // That means task implementation has allowed an unbalanced
255 // semaphore release.
256 throw new RuntimeException(
257 "ERROR: Task implementation did NOT balance its release.");
260 // Normal operation from here.
261 protocol.addCount(-1);
262 mOutstandingImageRefs--;
263 logWrapper("Ref release. Total refs = " + mOutstandingImageRefs);
264 if (protocol.getCount() == 0) {
265 // Image is ready to be released
266 // Remove the image from the map so that it may be submitted
268 mImageSemaphoreMap.remove(img);
270 // Conditionally close the image, specified by initial
272 if (protocol.closeOnRelease) {
273 closeImageExecutorSafe(img, executor);
274 logWrapper("Ref release close.");
277 // Conditionally signal the blocking thread to go.
278 if (protocol.blockUntilRelease) {
282 // Image is still being held by other tasks.
283 // Otherwise, update the semaphore
284 mImageSemaphoreMap.put(img, protocol);
290 * Spawns dependent tasks from internal implementation of a set of tasks. If
291 * a dependent task does NOT require the image reference, it should be
292 * passed a null pointer as an image reference. In general, this method
293 * should be called after the task has completed its own computations, but
294 * before it has released its own image reference (via the
295 * releaseSemaphoreReference call).
297 * @param tasks The set of tasks to be run
298 * @return whether tasks are successfully submitted.
301 public boolean appendTasks(ImageToProcess img, Set<TaskImageContainer> tasks) {
302 // Make sure that referred images are all the same, if it exists.
303 // And count how image references need to be kept track of.
304 int countImageRefs = numPropagatedImageReferences(img, tasks);
307 // If you're still holding onto the reference, make sure you keep
309 incrementSemaphoreReferenceCount(img, countImageRefs);
312 // Update the done count on the new tasks.
313 incrementTaskDone(tasks);
315 scheduleTasks(tasks);
320 * Spawns a single dependent task from internal implementation of a task.
322 * @param task The task to be run
323 * @return whether tasks are successfully submitted.
326 public boolean appendTasks(ImageToProcess img, TaskImageContainer task) {
327 Set<TaskImageContainer> tasks = new HashSet<TaskImageContainer>(1);
329 return appendTasks(img, tasks);
333 * Implements that top-level image single task submission that is defined by
334 * the ImageConsumer interface w/o Runnable to executed.
336 * @param img Image required by the task
337 * @param task Task to be run
338 * @param blockUntilImageRelease If true, call blocks until the object img
339 * is no longer referred by any task. If false, call is
341 * @param closeOnImageRelease If true, images is closed when the object img
342 * is is no longer referred by any task. If false, After an image
343 * is submitted, it should never be submitted again to the
344 * interface until all tasks and their spawned tasks are
346 * @return whether jobs were enqueued to the ImageBackend.
349 public boolean receiveImage(ImageToProcess img, TaskImageContainer task,
350 boolean blockUntilImageRelease, boolean closeOnImageRelease)
351 throws InterruptedException {
352 return receiveImage(img, task, blockUntilImageRelease, closeOnImageRelease,
353 Optional.<Runnable> absent());
357 * Implements that top-level image single task submission that is defined by
358 * the ImageConsumer interface.
360 * @param img Image required by the task
361 * @param task Task to be run
362 * @param blockUntilImageRelease If true, call blocks until the object img
363 * is no longer referred by any task. If false, call is
365 * @param closeOnImageRelease If true, images is closed when the object img
366 * is is no longer referred by any task. If false, After an image
367 * is submitted, it should never be submitted again to the
368 * interface until all tasks and their spawned tasks are
370 * @param runnableWhenDone Optional runnable to be executed when the set of
372 * @return whether jobs were enqueued to the ImageBackend.
375 public boolean receiveImage(ImageToProcess img, TaskImageContainer task,
376 boolean blockUntilImageRelease, boolean closeOnImageRelease,
377 Optional<Runnable> runnableWhenDone)
378 throws InterruptedException {
379 Set<TaskImageContainer> passTasks = new HashSet<TaskImageContainer>(1);
381 return receiveImage(img, passTasks, blockUntilImageRelease, closeOnImageRelease,
386 * Returns an informational string about the current status of ImageBackend,
387 * along with an approximate number of references being held.
389 * @return an informational string suitable to be dumped into logcat
392 public String toString() {
393 return "ImageBackend Status BEGIN:\n" +
394 "Shadow Image Map Size = " + mShadowTaskMap.size() + "\n" +
395 "Image Semaphore Map Size = " + mImageSemaphoreMap.size() + "\n" +
396 "OutstandingImageRefs = " + mOutstandingImageRefs + "\n" +
397 "Proxy Listener Map Size = " + mProxyListener.getMapSize() + "\n" +
398 "Proxy Listener = " + mProxyListener.getNumRegisteredListeners() + "\n" +
399 "ImageBackend Status END:\n";
403 * Implements that top-level image single task submission that is defined by
404 * the ImageConsumer interface.
406 * @param img Image required by the task
407 * @param tasks A set of Tasks to be run
408 * @param blockUntilImageRelease If true, call blocks until the object img
409 * is no longer referred by any task. If false, call is
411 * @param closeOnImageRelease If true, images is closed when the object img
412 * is is no longer referred by any task. If false, After an image
413 * is submitted, it should never be submitted again to the
414 * interface until all tasks and their spawned tasks are
416 * @param runnableWhenDone Optional runnable to be executed when the set of
418 * @return whether receiveImage succeeded. Generally, only happens when the
419 * image reference is null or the task set is empty.
420 * @throws InterruptedException occurs when call is set to be blocking and
424 public boolean receiveImage(ImageToProcess img, Set<TaskImageContainer> tasks,
425 boolean blockUntilImageRelease, boolean closeOnImageRelease,
426 Optional<Runnable> runnableWhenDone)
427 throws InterruptedException {
429 // Short circuit if no tasks submitted.
430 if (tasks == null || tasks.size() <= 0) {
435 // TODO: Determine whether you need to be so strict at the top level
436 throw new RuntimeException("ERROR: Initial call must reference valid Image!");
439 // Make sure that referred images are all the same, if it exists.
440 // And count how image references need to be kept track of.
441 int countImageRefs = numPropagatedImageReferences(img, tasks);
443 // Initialize the counters for process-level tasks
444 initializeTaskDone(tasks, runnableWhenDone);
446 // Set the semaphore, given that the number of tasks that need to be
448 // and the boolean flags for imaging closing and thread blocking
449 ImageReleaseProtocol protocol = setSemaphoreReferenceCount(img, countImageRefs,
450 blockUntilImageRelease, closeOnImageRelease);
452 // Put the tasks on their respective queues.
453 scheduleTasks(tasks);
455 // Implement blocking if required
456 if (protocol.blockUntilRelease) {
464 * Implements that top-level image task submission short-cut that is defined
465 * by the ImageConsumer interface.
467 * @param img Image required by the task
468 * @param executor Executor to run events and image closes, in case of
470 * @param processingFlags Magical bit vector that specifies jobs to be run
471 * After an image is submitted, it should never be submitted
472 * again to the interface until all tasks and their spawned tasks
474 * @param imageProcessorListener Optional listener to automatically register
475 * at the job task and unregister after all tasks are done
476 * @return whether receiveImage succeeded. Generally, only happens when the
477 * image reference is null or the task set is empty.
478 * @throws InterruptedException occurs when call is set to be blocking and
482 public boolean receiveImage(ImageToProcess img, Executor executor,
483 Set<ImageTaskFlags> processingFlags, CaptureSession session,
484 Optional<ImageProcessorListener> imageProcessorListener)
485 throws InterruptedException {
487 // Uncomment for occasional debugging
488 // Log.v(TAG, toString());
490 Set<TaskImageContainer> tasksToExecute = new HashSet<TaskImageContainer>();
493 // No data to process, just pure message.
497 // Now add the pre-mixed versions of the tasks.
499 if (processingFlags.contains(ImageTaskFlags.COMPRESS_TO_JPEG_AND_WRITE_TO_DISK)) {
500 if (processingFlags.contains(ImageTaskFlags.CREATE_EARLY_FILMSTRIP_PREVIEW)) {
501 // Request job that creates both filmstrip thumbnail from YUV,
502 // JPEG compression of the YUV Image, and writes the result to
504 tasksToExecute.add(new TaskPreviewChainedJpeg(img, executor, this, session,
505 FILMSTRIP_THUMBNAIL_TARGET_SIZE, mByteBufferDirectPool));
507 // Request job that only does JPEG compression and writes the
509 tasksToExecute.add(new TaskCompressImageToJpeg(img, executor, this, session,
510 mByteBufferDirectPool));
514 if (processingFlags.contains(ImageTaskFlags.CONVERT_TO_RGB_PREVIEW)) {
515 // Add an additional type of task to the appropriate queue.
516 tasksToExecute.add(new TaskConvertImageToRGBPreview(img, executor,
517 this, TaskImageContainer.ProcessingPriority.FAST, session,
518 mTinyThumbnailTargetSize,
519 TaskConvertImageToRGBPreview.ThumbnailShape.SQUARE_ASPECT_CIRCULAR_INSET));
522 // Wrap the listener in a runnable that will be fired when all tasks are
524 final Optional<Runnable> runnableOptional;
525 if (imageProcessorListener.isPresent()) {
526 final ImageProcessorListener finalImageProcessorListener = imageProcessorListener.get();
527 Runnable unregisterRunnable = new Runnable() {
530 getProxyListener().unregisterListener(finalImageProcessorListener);
533 runnableOptional = Optional.of(unregisterRunnable);
535 runnableOptional = Optional.<Runnable> absent();
538 if (receiveImage(img, tasksToExecute,
539 processingFlags.contains(ImageTaskFlags.BLOCK_UNTIL_ALL_TASKS_RELEASE),
540 processingFlags.contains(ImageTaskFlags.CLOSE_ON_ALL_TASKS_RELEASE),
542 if (imageProcessorListener.isPresent()) {
543 getProxyListener().registerListener(imageProcessorListener.get(), img.proxy);
552 * Factory functions, in case, you want some shake and bake functionality.
554 public TaskConvertImageToRGBPreview createTaskConvertImageToRGBPreview(
555 ImageToProcess image, Executor executor, ImageBackend imageBackend,
556 CaptureSession session, Size targetSize,
557 TaskConvertImageToRGBPreview.ThumbnailShape thumbnailShape) {
558 return new TaskConvertImageToRGBPreview(image, executor, imageBackend,
559 TaskImageContainer.ProcessingPriority.FAST, session,
560 mTinyThumbnailTargetSize, thumbnailShape);
563 public TaskCompressImageToJpeg createTaskCompressImageToJpeg(ImageToProcess image,
564 Executor executor, ImageBackend imageBackend, CaptureSession session) {
565 return new TaskCompressImageToJpeg(image, executor, imageBackend, session,
566 mByteBufferDirectPool);
570 * Blocks and waits for all tasks to complete.
573 public void shutdown() {
574 mThreadPoolSlow.shutdown();
575 mThreadPoolFast.shutdown();
579 * For a given set of starting tasks, initialize the associated sessions
580 * with a proper blocking semaphore and value of number of tasks to be run.
581 * For each semaphore, a ImageShadowTask will be instantiated and enqueued
582 * onto the selected ProcessingSerivceManager.
584 * @param tasks The set of ImageContainer tasks to be run on ImageBackend
586 protected void initializeTaskDone(Set<TaskImageContainer> tasks,
587 Optional<Runnable> runnableWhenDone) {
588 Set<CaptureSession> sessionSet = new HashSet<>();
589 Map<CaptureSession, Integer> sessionTaskCount = new HashMap<>();
591 // Create a set w/ no session duplicates and count them
592 for (TaskImageContainer task : tasks) {
593 sessionSet.add(task.mSession);
594 Integer currentCount = sessionTaskCount.get(task.mSession);
595 if (currentCount == null) {
596 sessionTaskCount.put(task.mSession, 1);
598 sessionTaskCount.put(task.mSession, currentCount + 1);
602 // Create a new blocking semaphore for each set of tasks on a given
604 synchronized (mShadowTaskMap) {
605 for (CaptureSession captureSession : sessionSet) {
606 BlockSignalProtocol protocol = new BlockSignalProtocol();
607 protocol.setCount(sessionTaskCount.get(captureSession));
608 final ImageShadowTask shadowTask;
609 shadowTask = new ImageShadowTask(protocol, captureSession,
611 mShadowTaskMap.put(captureSession, shadowTask);
612 mProcessingTaskConsumer.enqueueTask(shadowTask);
618 * For ImageBackend tasks that spawn their own tasks, increase the semaphore
619 * count to take into account the new tasks being spawned.
621 * @param tasks The set of tasks to be spawned.
623 protected void incrementTaskDone(Set<TaskImageContainer> tasks) throws RuntimeException {
624 // TODO: Add invariant test so that all sessions are the same.
625 synchronized (mShadowTaskMap) {
626 for (TaskImageContainer task : tasks) {
627 ImageShadowTask shadowTask = mShadowTaskMap.get(task.mSession);
628 if (shadowTask == null) {
629 throw new RuntimeException(
630 "Session NOT previously registered."
631 + " ImageShadowTask booking-keeping is incorrect.");
633 shadowTask.getProtocol().addCount(1);
639 * Decrement the semaphore count of the ImageShadowTask. Should be called
640 * when a task completes its processing in ImageBackend.
642 * @param imageShadowTask The ImageShadow task that contains the blocking
644 * @return whether all the tasks associated with an ImageShadowTask are done
646 protected boolean decrementTaskDone(ImageShadowTask imageShadowTask) {
647 synchronized (mShadowTaskMap) {
648 int remainingTasks = imageShadowTask.getProtocol().addCount(-1);
649 if (remainingTasks == 0) {
650 mShadowTaskMap.remove(imageShadowTask.getSession());
651 imageShadowTask.getProtocol().signal();
661 * Puts the tasks on the specified queue. May be more complicated in the
664 * @param tasks The set of tasks to be run
666 protected void scheduleTasks(Set<TaskImageContainer> tasks) {
667 synchronized (mShadowTaskMap) {
668 for (TaskImageContainer task : tasks) {
669 ImageShadowTask shadowTask = mShadowTaskMap.get(task.mSession);
670 if (shadowTask == null) {
671 throw new IllegalStateException("Scheduling a task with a unknown session.");
673 // Before scheduling, wrap TaskImageContainer inside of the
674 // TaskDoneWrapper to add
675 // instrumentation for managing ImageShadowTasks
676 switch (task.getProcessingPriority()) {
678 mThreadPoolFast.execute(new TaskDoneWrapper(this, shadowTask, task));
681 mThreadPoolAverage.execute(new TaskDoneWrapper(this, shadowTask, task));
684 mThreadPoolSlow.execute(new TaskDoneWrapper(this, shadowTask, task));
687 mThreadPoolSlow.execute(new TaskDoneWrapper(this, shadowTask, task));
695 * Initializes the semaphore count for the image
697 * @return The protocol object that keeps tracks of the image reference
698 * count and actions to be taken on release.
700 protected ImageReleaseProtocol setSemaphoreReferenceCount(ImageToProcess img, int count,
701 boolean blockUntilRelease, boolean closeOnRelease) throws RuntimeException {
702 synchronized (mImageSemaphoreMap) {
703 if (mImageSemaphoreMap.get(img) != null) {
704 throw new RuntimeException(
705 "ERROR: Rewriting of Semaphore Lock."
706 + " Image references may not freed properly");
709 // Create the new booking-keeping object.
710 ImageReleaseProtocol protocol = new ImageReleaseProtocol(blockUntilRelease,
712 protocol.setCount(count);
714 mImageSemaphoreMap.put(img, protocol);
715 mOutstandingImageRefs += count;
716 mOutstandingImageOpened++;
717 logWrapper("Received an opened image: " + mOutstandingImageOpened + "/"
718 + mOutstandingImageClosed);
719 logWrapper("Setting an image reference count of " + count + " Total refs = "
720 + mOutstandingImageRefs);
726 * Increments the semaphore count for the image. Should ONLY be internally
727 * via appendTasks by internal tasks. Otherwise, image references could get
730 * @param img The Image associated with the set of tasks running on it.
731 * @param count The number of tasks to be added
732 * @throws RuntimeException Indicates image Closing Bookkeeping is screwed
735 protected void incrementSemaphoreReferenceCount(ImageToProcess img, int count)
736 throws RuntimeException {
737 synchronized (mImageSemaphoreMap) {
738 ImageReleaseProtocol protocol = mImageSemaphoreMap.get(img);
739 if (mImageSemaphoreMap.get(img) == null) {
740 throw new RuntimeException(
741 "Image Reference has already been released or has never been held.");
744 protocol.addCount(count);
745 mImageSemaphoreMap.put(img, protocol);
747 mOutstandingImageRefs += count;
752 * Close an Image with a executor if it's available and does the proper
753 * booking keeping on the object.
755 * @param img Image to be closed
756 * @param executor Executor to be used, if executor is null, the close is
757 * run on the task thread
759 private void closeImageExecutorSafe(final ImageToProcess img, Executor executor) {
760 Runnable closeTask = new Runnable() {
764 mOutstandingImageClosed++;
765 logWrapper("Release of image occurred. Good fun. " + "Total Images Open/Closed = "
766 + mOutstandingImageOpened + "/" + mOutstandingImageClosed);
769 if (executor == null) {
770 // Just run it on the main thread.
773 executor.execute(closeTask);
778 * Calculates the number of new Image references in a set of dependent
779 * tasks. Checks to make sure no new image references are being introduced.
781 * @param tasks The set of dependent tasks to be run
783 private int numPropagatedImageReferences(ImageToProcess img, Set<TaskImageContainer> tasks)
784 throws RuntimeException {
785 int countImageRefs = 0;
786 for (TaskImageContainer task : tasks) {
787 if (task.mImage != null && task.mImage != img) {
788 throw new RuntimeException("ERROR: Spawned tasks cannot reference new images!");
791 if (task.mImage != null) {
796 return countImageRefs;
800 * Simple wrapper task to instrument when tasks ends so that ImageBackend
801 * can fire events when set of tasks created by a ReceiveImage call have all
804 private class TaskDoneWrapper implements Runnable {
805 private final ImageBackend mImageBackend;
806 private final ImageShadowTask mImageShadowTask;
807 private final TaskImageContainer mWrappedTask;
812 * @param imageBackend ImageBackend that the task is running on
813 * @param imageShadowTask ImageShadowTask that is blocking on the
814 * completion of the task
815 * @param wrappedTask The task to be run w/o instrumentation
817 public TaskDoneWrapper(ImageBackend imageBackend, ImageShadowTask imageShadowTask,
818 TaskImageContainer wrappedTask) {
819 mImageBackend = imageBackend;
820 mImageShadowTask = imageShadowTask;
821 mWrappedTask = wrappedTask;
825 * Adds instrumentation that runs when a TaskImageContainer completes.
831 if (mImageBackend.decrementTaskDone(mImageShadowTask)) {
832 // If you're the last one...
833 Runnable doneRunnable = mImageShadowTask.getRunnableWhenDone();
834 if (doneRunnable != null) {
835 if (mWrappedTask.mExecutor == null) {
838 mWrappedTask.mExecutor.execute(doneRunnable);
846 * Encapsulates all synchronization for semaphore signaling and blocking.
848 static public class BlockSignalProtocol {
851 private final ReentrantLock mLock = new ReentrantLock();
853 private Condition mSignal;
855 public void setCount(int value) {
861 public int getCount() {
869 public int addCount(int value) {
879 BlockSignalProtocol() {
881 mSignal = mLock.newCondition();
884 public void block() throws InterruptedException {
888 // Spin to deal with spurious signals.
891 } catch (InterruptedException e) {
892 // TODO: on interruption, figure out what to do.
899 public void signal() {
908 * A simple tuple class to keep track of image reference, and whether to
909 * block and/or close on final image release. Instantiated on every task
912 static public class ImageReleaseProtocol extends BlockSignalProtocol {
914 public final boolean blockUntilRelease;
916 public final boolean closeOnRelease;
918 ImageReleaseProtocol(boolean block, boolean close) {
920 blockUntilRelease = block;
921 closeOnRelease = close;
926 // Thread factories for a default constructor
927 private class FastThreadFactory implements ThreadFactory {
929 public Thread newThread(Runnable r) {
930 Thread t = new AndroidPriorityThread(FAST_THREAD_PRIORITY, r);
935 private class AverageThreadFactory implements ThreadFactory {
937 public Thread newThread(Runnable r) {
938 Thread t = new AndroidPriorityThread(AVERAGE_THREAD_PRIORITY, r);
943 private class SlowThreadFactory implements ThreadFactory {
945 public Thread newThread(Runnable r) {
946 Thread t = new AndroidPriorityThread(SLOW_THREAD_PRIORITY, r);