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.camera.one.v2;
18 
19 import android.annotation.TargetApi;
20 import android.hardware.camera2.CameraCaptureSession;
21 import android.hardware.camera2.CaptureRequest;
22 import android.hardware.camera2.CaptureResult;
23 import android.hardware.camera2.CaptureResult.Key;
24 import android.hardware.camera2.TotalCaptureResult;
25 import android.media.Image;
26 import android.media.ImageReader;
27 import android.os.Build;
28 import android.os.Handler;
29 import android.os.SystemClock;
30 import android.util.Pair;
31 
32 import com.android.camera.debug.Log;
33 import com.android.camera.debug.Log.Tag;
34 import com.android.camera.util.ConcurrentSharedRingBuffer;
35 import com.android.camera.util.ConcurrentSharedRingBuffer.PinStateListener;
36 import com.android.camera.util.ConcurrentSharedRingBuffer.Selector;
37 import com.android.camera.util.ConcurrentSharedRingBuffer.SwapTask;
38 import com.android.camera.util.Task;
39 
40 import java.util.Collections;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Set;
44 import java.util.concurrent.ConcurrentHashMap;
45 import java.util.concurrent.Executor;
46 import java.util.concurrent.RejectedExecutionException;
47 import java.util.concurrent.atomic.AtomicInteger;
48 
49 /**
50  * Implements {@link android.media.ImageReader.OnImageAvailableListener} and
51  * {@link android.hardware.camera2.CameraCaptureSession.CaptureListener} to
52  * store the results of capture requests (both {@link Image}s and
53  * {@link TotalCaptureResult}s in a ring-buffer from which they may be saved.
54  * <br>
55  * This also manages the lifecycle of {@link Image}s within the application as
56  * they are passed in from the lower-level camera2 API.
57  */
58 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
59 public class ImageCaptureManager extends CameraCaptureSession.CaptureCallback implements
60         ImageReader.OnImageAvailableListener {
61     /**
62      * Callback to listen for changes to the ability to capture an existing
63      * image from the internal ring-buffer.
64      */
65     public interface CaptureReadyListener {
66         /**
67          * Called whenever the ability to capture an existing image from the
68          * ring-buffer changes. Calls to {@link #tryCaptureExistingImage} are
69          * more likely to succeed or fail depending on the value passed in to
70          * this function.
71          *
72          * @param capturePossible true if capture is more-likely to be possible,
73          *            false if capture is less-likely to be possible.
74          */
onReadyStateChange(boolean capturePossible)75         public void onReadyStateChange(boolean capturePossible);
76     }
77 
78     /**
79      * Callback for listening to changes to individual metadata values.
80      */
81     public static interface MetadataChangeListener {
82         /**
83          * This will be called whenever a metadata value changes.
84          * Implementations should not take too much time to execute since this
85          * will be called faster than the camera's frame rate.
86          *
87          * @param key the {@link CaptureResult} key this listener listens for.
88          * @param second the previous value, or null if no such value existed.
89          *            The type will be that associated with the
90          *            {@link android.hardware.camera2.CaptureResult.Key} this
91          *            listener is bound to.
92          * @param newValue the new value. The type will be that associated with
93          *            the {@link android.hardware.camera2.CaptureResult.Key}
94          *            this listener is bound to.
95          * @param result the CaptureResult containing the new value
96          */
onImageMetadataChange(Key<?> key, Object second, Object newValue, CaptureResult result)97         public void onImageMetadataChange(Key<?> key, Object second, Object newValue,
98                 CaptureResult result);
99     }
100 
101     /**
102      * Callback for saving an image.
103      */
104     public interface ImageCaptureListener {
105         /**
106          * Called with the {@link Image} and associated
107          * {@link TotalCaptureResult}. A typical implementation would save this
108          * to disk.
109          * <p>
110          * Note: Implementations must be thread-safe and must not close the
111          * image.
112          * </p>
113          */
onImageCaptured(Image image, TotalCaptureResult captureResult)114         public void onImageCaptured(Image image, TotalCaptureResult captureResult);
115     }
116 
117     /**
118      * Callback for placing constraints on which images to capture. See
119      * {@link #tryCaptureExistingImage} and {@link #captureNextImage}.
120      */
121     public static interface CapturedImageConstraint {
122         /**
123          * Implementations should return true if the provided
124          * TotalCaptureResults satisfies constraints necessary for the intended
125          * image capture. For example, a constraint may return false if
126          * {@captureResult} indicates that the lens was moving during image
127          * capture.
128          *
129          * @param captureResult The metadata associated with the image.
130          * @return true if this image satisfies the constraint and can be
131          *         captured, false otherwise.
132          */
satisfiesConstraint(TotalCaptureResult captureResult)133         boolean satisfiesConstraint(TotalCaptureResult captureResult);
134     }
135 
136     /**
137      * Holds an {@link Image} and {@link TotalCaptureResult} pair which may be
138      * added asynchronously.
139      */
140     private class CapturedImage {
141         /**
142          * The Image and TotalCaptureResult may be received at different times
143          * (via the onImageAvailableListener and onCaptureProgressed callbacks,
144          * respectively).
145          */
146         private Image mImage = null;
147         private TotalCaptureResult mMetadata = null;
148 
149         /**
150          * Resets the object, closing and removing any existing image and
151          * metadata.
152          */
reset()153         public void reset() {
154             if (mImage != null) {
155                 mImage.close();
156                 int numOpenImages = mNumOpenImages.decrementAndGet();
157                 if (DEBUG_PRINT_OPEN_IMAGE_COUNT) {
158                     Log.v(TAG, "Closed an image. Number of open images = " + numOpenImages);
159                 }
160             }
161 
162             mImage = null;
163 
164             mMetadata = null;
165         }
166 
167         /**
168          * @return true if both the image and metadata are present, false
169          *         otherwise.
170          */
isComplete()171         public boolean isComplete() {
172             return mImage != null && mMetadata != null;
173         }
174 
175         /**
176          * Adds the image. Note that this can only be called once before a
177          * {@link #reset()} is necessary.
178          *
179          * @param image the {@Link Image} to add.
180          */
addImage(Image image)181         public void addImage(Image image) {
182             if (mImage != null) {
183                 throw new IllegalArgumentException(
184                         "Unable to add an Image when one already exists.");
185             }
186             mImage = image;
187         }
188 
189         /**
190          * Retrieves the {@link Image} if it has been added, returns null if it
191          * is not available yet.
192          */
tryGetImage()193         public Image tryGetImage() {
194             return mImage;
195         }
196 
197         /**
198          * Adds the metadata. Note that this can only be called once before a
199          * {@link #reset()} is necessary.
200          *
201          * @param metadata the {@Link TotalCaptureResult} to add.
202          */
addMetadata(TotalCaptureResult metadata)203         public void addMetadata(TotalCaptureResult metadata) {
204             if (mMetadata != null) {
205                 throw new IllegalArgumentException(
206                         "Unable to add a TotalCaptureResult when one already exists.");
207             }
208             mMetadata = metadata;
209         }
210 
211         /**
212          * Retrieves the {@link TotalCaptureResult} if it has been added,
213          * returns null if it is not available yet.
214          */
tryGetMetadata()215         public TotalCaptureResult tryGetMetadata() {
216             return mMetadata;
217         }
218 
219         /**
220          * Returs the timestamp of the image if present, -1 otherwise.
221          */
tryGetTimestamp()222         public long tryGetTimestamp() {
223             if (mImage != null) {
224                 return mImage.getTimestamp();
225             }
226             if (mMetadata != null) {
227                 return mMetadata.get(TotalCaptureResult.SENSOR_TIMESTAMP);
228             }
229             return -1;
230         }
231     }
232 
233     private static final Tag TAG = new Tag("ZSLImageListener");
234 
235     /**
236      * If true, the number of open images will be printed to LogCat every time
237      * an image is opened or closed.
238      */
239     private static final boolean DEBUG_PRINT_OPEN_IMAGE_COUNT = false;
240 
241     /**
242      * The maximum duration for an onImageAvailable() callback before debugging
243      * output is printed. This is a little under 1/30th of a second to enable
244      * detecting jank in the preview stream caused by {@link #onImageAvailable}
245      * taking too long to return.
246      */
247     private static final long DEBUG_MAX_IMAGE_CALLBACK_DUR = 25;
248 
249     /**
250      * If spacing between onCaptureCompleted() callbacks is lower than this
251      * value, camera operations at the Java level have stalled, and are now
252      * catching up. In milliseconds.
253      */
254     private static final long DEBUG_INTERFRAME_STALL_WARNING = 5;
255 
256     /**
257      * Last called to onCaptureCompleted() in SystemClock.uptimeMillis().
258      */
259     private long mDebugLastOnCaptureCompletedMillis = 0;
260 
261     /**
262      * Number of frames in a row exceeding DEBUG_INTERFRAME_STALL_WARNING.
263      */
264     private long mDebugStalledFrameCount = 0;
265 
266     /**
267      * Stores the ring-buffer of captured images.<br>
268      * Note that this takes care of thread-safe reference counting of images to
269      * ensure that they are never leaked by the app.
270      */
271     private final ConcurrentSharedRingBuffer<CapturedImage> mCapturedImageBuffer;
272 
273     /** Track the number of open images for debugging purposes. */
274     private final AtomicInteger mNumOpenImages = new AtomicInteger(0);
275 
276     /**
277      * The handler used to invoke light-weight listeners:
278      * {@link CaptureReadyListener} and {@link MetadataChangeListener}.
279      */
280     private final Handler mListenerHandler;
281 
282     /**
283      * The executor used to invoke {@link ImageCaptureListener}. Note that this
284      * is different from mListenerHandler because a typical ImageCaptureListener
285      * will compress the image to jpeg, and we may wish to execute these tasks
286      * on multiple threads.
287      */
288     private final Executor mImageCaptureListenerExecutor;
289 
290     /**
291      * The set of constraints which must be satisfied for a newly acquired image
292      * to be captured and sent to {@link #mPendingImageCaptureCallback}. null if
293      * there is no pending capture request.
294      */
295     private List<ImageCaptureManager.CapturedImageConstraint> mPendingImageCaptureConstraints;
296 
297     /**
298      * The callback to be invoked upon successfully capturing a newly-acquired
299      * image which satisfies {@link #mPendingImageCaptureConstraints}. null if
300      * there is no pending capture request.
301      */
302     private ImageCaptureManager.ImageCaptureListener mPendingImageCaptureCallback;
303 
304     /**
305      * Map from CaptureResult key to the frame number of the capture result
306      * containing the most recent value for this key and the most recent value
307      * of the key.
308      */
309     private final Map<Key<?>, Pair<Long, Object>>
310             mMetadata = new ConcurrentHashMap<CaptureResult.Key<?>, Pair<Long, Object>>();
311 
312     /**
313      * The set of callbacks to be invoked when an entry in {@link #mMetadata} is
314      * changed.
315      */
316     private final Map<Key<?>, Set<MetadataChangeListener>>
317             mMetadataChangeListeners = new ConcurrentHashMap<Key<?>, Set<MetadataChangeListener>>();
318 
319     /**
320      * @param maxImages the maximum number of images provided by the
321      *            {@link ImageReader}. This must be greater than 2.
322      * @param listenerHandler the handler on which to invoke listeners. Note
323      *            that this should probably be on a different thread than the
324      *            one used for camera operations, such as capture requests and
325      *            OnImageAvailable listeners, to avoid stalling the preview.
326      * @param imageCaptureListenerExecutor the executor on which to invoke image
327      *            capture listeners, {@link ImageCaptureListener}.
328      */
ImageCaptureManager(int maxImages, Handler listenerHandler, Executor imageCaptureListenerExecutor)329     ImageCaptureManager(int maxImages, Handler listenerHandler,
330             Executor imageCaptureListenerExecutor) {
331         // Ensure that there are always 2 images available for the framework to
332         // continue processing frames.
333         // TODO Could we make this tighter?
334         mCapturedImageBuffer = new ConcurrentSharedRingBuffer<ImageCaptureManager.CapturedImage>(
335                 maxImages - 2);
336 
337         mListenerHandler = listenerHandler;
338         mImageCaptureListenerExecutor = imageCaptureListenerExecutor;
339     }
340 
341     /**
342      * See {@link CaptureReadyListener}.
343      */
setCaptureReadyListener(final CaptureReadyListener listener)344     public void setCaptureReadyListener(final CaptureReadyListener listener) {
345         mCapturedImageBuffer.setListener(mListenerHandler,
346                 new PinStateListener() {
347                 @Override
348                     public void onPinStateChange(boolean pinsAvailable) {
349                         listener.onReadyStateChange(pinsAvailable);
350                     }
351                 });
352     }
353 
354     /**
355      * Adds a metadata stream listener associated with the given key.
356      *
357      * @param key the key of the metadata to track.
358      * @param listener the listener to be invoked when the value associated with
359      *            key changes.
360      */
addMetadataChangeListener(Key<T> key, MetadataChangeListener listener)361     public <T> void addMetadataChangeListener(Key<T> key, MetadataChangeListener listener) {
362         if (!mMetadataChangeListeners.containsKey(key)) {
363             // Listeners may be added to this set from a different thread than
364             // that which must iterate over this set to invoke the listeners.
365             // Therefore, we need a thread save hash set.
366             mMetadataChangeListeners.put(key,
367                     Collections.newSetFromMap(new ConcurrentHashMap<
368                             ImageCaptureManager.MetadataChangeListener, Boolean>()));
369         }
370         mMetadataChangeListeners.get(key).add(listener);
371     }
372 
373     /**
374      * Removes the metadata stream listener associated with the given key.
375      *
376      * @param key the key associated with the metadata to track.
377      * @param listener the listener to be invoked when the value associated with
378      *            key changes.
379      * @return true if the listener was removed, false if no such listener had
380      *         been added.
381      */
removeMetadataChangeListener(Key<T> key, MetadataChangeListener listener)382     public <T> boolean removeMetadataChangeListener(Key<T> key, MetadataChangeListener listener) {
383         if (!mMetadataChangeListeners.containsKey(key)) {
384             return false;
385         } else {
386             return mMetadataChangeListeners.get(key).remove(listener);
387         }
388     }
389 
390     @Override
onCaptureProgressed(CameraCaptureSession session, CaptureRequest request, final CaptureResult partialResult)391     public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request,
392             final CaptureResult partialResult) {
393         updateMetadataChangeListeners(partialResult);
394     }
395 
396     @Override
onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, final TotalCaptureResult result)397     public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
398             final TotalCaptureResult result) {
399         final long timestamp = result.get(TotalCaptureResult.SENSOR_TIMESTAMP);
400 
401         updateMetadataChangeListeners(result);
402 
403         // Detect camera thread stall.
404         long now = SystemClock.uptimeMillis();
405         if (now - mDebugLastOnCaptureCompletedMillis < DEBUG_INTERFRAME_STALL_WARNING) {
406             Log.e(TAG, "Camera thread has stalled for " + ++mDebugStalledFrameCount +
407                     " frames at # " + result.getFrameNumber() + ".");
408         } else {
409             mDebugStalledFrameCount = 0;
410         }
411         mDebugLastOnCaptureCompletedMillis = now;
412 
413         // Find the CapturedImage in the ring-buffer and attach the
414         // TotalCaptureResult to it.
415         // See documentation for swapLeast() for details.
416         boolean swapSuccess = doMetaDataSwap(result, timestamp);
417         if (!swapSuccess) {
418             // Do nothing on failure to swap in.
419             Log.v(TAG, "Unable to add new image metadata to ring-buffer.");
420         }
421 
422         tryExecutePendingCaptureRequest(timestamp);
423     }
424 
updateMetadataChangeListeners(final CaptureResult result)425     private void updateMetadataChangeListeners(final CaptureResult result) {
426         long frameNumber = result.getFrameNumber();
427 
428         // Update mMetadata for whichever keys are present, if this frame is
429         // supplying newer values.
430         for (final Key<?> key : result.getKeys()) {
431             Pair<Long, Object> oldEntry = mMetadata.get(key);
432             final Object oldValue = (oldEntry != null) ? oldEntry.second : null;
433 
434             boolean newerValueAlreadyExists = oldEntry != null
435                     && frameNumber < oldEntry.first;
436             if (newerValueAlreadyExists) {
437                 continue;
438             }
439 
440             final Object newValue = result.get(key);
441             mMetadata.put(key, new Pair<Long, Object>(frameNumber, newValue));
442 
443             // If the value has changed, call the appropriate listeners, if
444             // any exist.
445             if (oldValue == newValue || !mMetadataChangeListeners.containsKey(key)) {
446                 continue;
447             }
448 
449             for (final MetadataChangeListener listener :
450                     mMetadataChangeListeners.get(key)) {
451                 mListenerHandler.post(new Runnable() {
452                     @Override
453                     public void run() {
454                         listener.onImageMetadataChange(key, oldValue, newValue,
455                                 result);
456                     }
457                 });
458             }
459         }
460     }
461 
462     private boolean doMetaDataSwap(final TotalCaptureResult newMetadata, final long timestamp) {
463         return mCapturedImageBuffer.swapLeast(timestamp,
464                 new SwapTask<CapturedImage>() {
465                 @Override
466                     public CapturedImage create() {
467                         CapturedImage image = new CapturedImage();
468                         image.addMetadata(newMetadata);
469                         return image;
470                     }
471 
472                 @Override
473                     public CapturedImage swap(CapturedImage oldElement) {
474                         oldElement.reset();
475                         oldElement.addMetadata(newMetadata);
476                         return oldElement;
477                     }
478 
479                 @Override
480                     public void update(CapturedImage existingElement) {
481                         existingElement.addMetadata(newMetadata);
482                     }
483 
484                 @Override
485                     public long getSwapKey() {
486                         return -1;
487                     }
488                 });
489     }
490 
491     private boolean doImageSwap(final Image newImage) {
492         return mCapturedImageBuffer.swapLeast(newImage.getTimestamp(),
493                 new SwapTask<CapturedImage>() {
494                 @Override
495                     public CapturedImage create() {
496                         CapturedImage image = new CapturedImage();
497                         image.addImage(newImage);
498                         return image;
499                     }
500 
501                 @Override
502                     public CapturedImage swap(CapturedImage oldElement) {
503                         oldElement.reset();
504                         CapturedImage image = new CapturedImage();
505                         image.addImage(newImage);
506                         return image;
507                     }
508 
509                 @Override
510                     public void update(CapturedImage existingElement) {
511                         existingElement.addImage(newImage);
512                     }
513 
514                 @Override
515                     public long getSwapKey() {
516                         return -1;
517                     }
518                 });
519     }
520 
521     @Override
522     public void onImageAvailable(ImageReader reader) {
523         long startTime = SystemClock.currentThreadTimeMillis();
524 
525         final Image img = reader.acquireLatestImage();
526 
527         if (img != null) {
528             int numOpenImages = mNumOpenImages.incrementAndGet();
529             if (DEBUG_PRINT_OPEN_IMAGE_COUNT) {
530                 Log.v(TAG, "Acquired an image. Number of open images = " + numOpenImages);
531             }
532 
533             long timestamp = img.getTimestamp();
534             // Try to place the newly-acquired image into the ring buffer.
535             boolean swapSuccess = doImageSwap(img);
536             if (!swapSuccess) {
537                 // If we were unable to save the image to the ring buffer, we
538                 // must close it now.
539                 // We should only get here if the ring buffer is closed.
540                 img.close();
541                 numOpenImages = mNumOpenImages.decrementAndGet();
542                 if (DEBUG_PRINT_OPEN_IMAGE_COUNT) {
543                     Log.v(TAG, "Closed an image. Number of open images = " + numOpenImages);
544                 }
545             }
546 
547             tryExecutePendingCaptureRequest(timestamp);
548 
549             long endTime = SystemClock.currentThreadTimeMillis();
550             long totTime = endTime - startTime;
551             if (totTime > DEBUG_MAX_IMAGE_CALLBACK_DUR) {
552                 // If it takes too long to swap elements, we will start skipping
553                 // preview frames, resulting in visible jank.
554                 Log.v(TAG, "onImageAvailable() took " + totTime + "ms");
555             }
556         }
557     }
558 
559     /**
560      * Closes the listener, eventually freeing all currently-held {@link Image}
561      * s.
562      */
563     public void close() {
564         closeBuffer();
565     }
566 
567     /**
568      * Sets the pending image capture request, overriding any previous calls to
569      * {@link #captureNextImage} which have not yet been resolved. When the next
570      * available image which satisfies the given constraints can be captured,
571      * onImageCaptured will be invoked.
572      *
573      * @param onImageCaptured the callback which will be invoked with the
574      *            captured image.
575      * @param constraints the set of constraints which must be satisfied in
576      *            order for the image to be captured.
577      */
578     public void captureNextImage(final ImageCaptureListener onImageCaptured,
579             final List<CapturedImageConstraint> constraints) {
580         mPendingImageCaptureCallback = onImageCaptured;
581         mPendingImageCaptureConstraints = constraints;
582     }
583 
584     /**
585      * Tries to resolve any pending image capture requests.
586      *
587      * @param newImageTimestamp the timestamp of a newly-acquired image which
588      *            should be captured if appropriate and possible.
589      */
590     private void tryExecutePendingCaptureRequest(long newImageTimestamp) {
591         if (mPendingImageCaptureCallback != null) {
592             final Pair<Long, CapturedImage> pinnedImage = mCapturedImageBuffer.tryPin(
593                     newImageTimestamp);
594             if (pinnedImage != null) {
595                 CapturedImage image = pinnedImage.second;
596 
597                 if (!image.isComplete()) {
598                     mCapturedImageBuffer.release(pinnedImage.first);
599                     return;
600                 }
601 
602                 // Check to see if the image satisfies all constraints.
603                 TotalCaptureResult captureResult = image.tryGetMetadata();
604 
605                 if (mPendingImageCaptureConstraints != null) {
606                     for (CapturedImageConstraint constraint : mPendingImageCaptureConstraints) {
607                         if (!constraint.satisfiesConstraint(captureResult)) {
608                             mCapturedImageBuffer.release(pinnedImage.first);
609                             return;
610                         }
611                     }
612                 }
613 
614                 // If we get here, the image satisfies all the necessary
615                 // constraints.
616 
617                 if (tryExecuteCaptureOrRelease(pinnedImage, mPendingImageCaptureCallback)) {
618                     // If we successfully handed the image off to the callback,
619                     // remove the pending
620                     // capture request.
621                     mPendingImageCaptureCallback = null;
622                     mPendingImageCaptureConstraints = null;
623                 }
624             }
625         }
626     }
627 
628     /**
629      * Tries to capture an existing image from the ring-buffer, if one exists
630      * that satisfies the given constraint and can be pinned.
631      *
632      * @return true if the image could be captured, false otherwise.
633      */
634     public boolean tryCaptureExistingImage(final ImageCaptureListener onImageCaptured,
635             final List<CapturedImageConstraint> constraints) {
636         // The selector to use in choosing the image to capture.
637         Selector<ImageCaptureManager.CapturedImage> selector;
638 
639         if (constraints == null || constraints.isEmpty()) {
640             // If there are no constraints, use a trivial Selector.
641             selector = new Selector<ImageCaptureManager.CapturedImage>() {
642                     @Override
643                 public boolean select(CapturedImage image) {
644                     return true;
645                 }
646             };
647         } else {
648             // If there are constraints, create a Selector which will return
649             // true if all constraints
650             // are satisfied.
651             selector = new Selector<ImageCaptureManager.CapturedImage>() {
652                     @Override
653                 public boolean select(CapturedImage e) {
654                     // If this image already has metadata associated with it,
655                     // then use it.
656                     // Otherwise, we can't block until it's available, so assume
657                     // it doesn't
658                     // satisfy the required constraints.
659                     TotalCaptureResult captureResult = e.tryGetMetadata();
660 
661                     if (captureResult == null || e.tryGetImage() == null) {
662                         return false;
663                     }
664 
665                     for (CapturedImageConstraint constraint : constraints) {
666                         if (!constraint.satisfiesConstraint(captureResult)) {
667                             return false;
668                         }
669                     }
670                     return true;
671                 }
672             };
673         }
674 
675         // Acquire a lock (pin) on the most recent (greatest-timestamp) image in
676         // the ring buffer which satisfies our constraints.
677         // Note that this must be released as soon as we are done with it.
678         final Pair<Long, CapturedImage> toCapture = mCapturedImageBuffer.tryPinGreatestSelected(
679                 selector);
680 
681         return tryExecuteCaptureOrRelease(toCapture, onImageCaptured);
682     }
683 
684     /**
685      * Tries to execute the image capture callback with the pinned CapturedImage
686      * provided.
687      *
688      * @param toCapture The pinned CapturedImage to pass to the callback, or
689      *            release on failure.
690      * @param callback The callback to execute.
691      * @return true upon success, false upon failure and the release of the
692      *         pinned image.
693      */
694     private boolean tryExecuteCaptureOrRelease(final Pair<Long, CapturedImage> toCapture,
695             final ImageCaptureListener callback) {
696         if (toCapture == null) {
697             return false;
698         } else {
699             try {
700                 mImageCaptureListenerExecutor.execute(new Runnable() {
701                         @Override
702                     public void run() {
703                         try {
704                             CapturedImage img = toCapture.second;
705                             callback.onImageCaptured(img.tryGetImage(),
706                                     img.tryGetMetadata());
707                         } finally {
708                             mCapturedImageBuffer.release(toCapture.first);
709                         }
710                     }
711                 });
712             } catch (RejectedExecutionException e) {
713                 // We may get here if the thread pool has been closed.
714                 mCapturedImageBuffer.release(toCapture.first);
715                 return false;
716             }
717 
718             return true;
719         }
720     }
721 
722     /**
723      * Tries to capture a pinned image for the given key from the ring-buffer.
724      *
725      * @return the pair of (image, captureResult) if image is found, null
726      *         otherwise.
727      */
728     public Pair<Image, TotalCaptureResult>
729             tryCapturePinnedImage(long timestamp) {
730         final Pair<Long, CapturedImage> toCapture =
731                 mCapturedImageBuffer.tryGetPinned(timestamp);
732         Image pinnedImage = null;
733         TotalCaptureResult imageCaptureResult = null;
734         // Return an Image
735         if (toCapture != null && toCapture.second != null) {
736             pinnedImage = toCapture.second.tryGetImage();
737             imageCaptureResult = toCapture.second.tryGetMetadata();
738         }
739         return Pair.create(pinnedImage, imageCaptureResult);
740     }
741 
742     /**
743      * Clear the buffer and reserves <code>unpinnedReservedSlots</code> in the buffer.
744      *
745      * @param unpinnedReservedSlots the number of unpinned slots that are never
746      *            allowed to be pinned.
747      */
748     private void clearCapturedImageBuffer(int unpinnedReservedSlots) {
749         mCapturedImageBuffer.releaseAll();
750         closeBuffer();
751         try {
752             mCapturedImageBuffer.reopenBuffer(unpinnedReservedSlots);
753         } catch (InterruptedException e) {
754             e.printStackTrace();
755         }
756     }
757 
758     /**
759      * Closes the buffer and frees up any images in the buffer.
760      */
761     private void closeBuffer() {
762         try {
763             mCapturedImageBuffer.close(new Task<CapturedImage>() {
764                 @Override
765                 public void run(CapturedImage e) {
766                     e.reset();
767                 }
768             });
769         } catch (InterruptedException e) {
770             e.printStackTrace();
771         }
772     }
773 }
774