Home | History | Annotate | Download | only in v2
      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          */
     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          */
     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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    215         public TotalCaptureResult tryGetMetadata() {
    216             return mMetadata;
    217         }
    218 
    219         /**
    220          * Returs the timestamp of the image if present, -1 otherwise.
    221          */
    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      */
    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      */
    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      */
    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      */
    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
    391     public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request,
    392             final CaptureResult partialResult) {
    393         updateMetadataChangeListeners(partialResult);
    394     }
    395 
    396     @Override
    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 
    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