camera2: refactor LEGACY mode error handling.

Bug: 17431462
Bug: 17407537

- Add Surface format/size validation during configure.
- Update exception error codes used in binder calls.
- Report dropped requests, frames, captures, and device
  errors in binder callback properly.
- Fixes CameraDeviceTest errors for incorrect metering
  rectangle weight in template tests.
- Fixes CameraDeviceTest errors for missing
  noiseReductionMode field in template tests.
- Implement flush call.

Change-Id: I0da803bccf2bfb9b4c0cf61208e160a86c577497
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 1dc5533..2578093 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -51,7 +51,6 @@
  * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate
  */
 public class CameraDeviceImpl extends CameraDevice {
-
     private final String TAG;
     private final boolean DEBUG;
 
@@ -1136,7 +1135,6 @@
     }
 
     public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
-
         //
         // Constants below need to be kept up-to-date with
         // frameworks/av/include/camera/camera2/ICameraDeviceCallbacks.h
@@ -1149,34 +1147,29 @@
         /**
          * Camera has been disconnected
          */
-        static final int ERROR_CAMERA_DISCONNECTED = 0;
-
+        public static final int ERROR_CAMERA_DISCONNECTED = 0;
         /**
          * Camera has encountered a device-level error
          * Matches CameraDevice.StateCallback#ERROR_CAMERA_DEVICE
          */
-        static final int ERROR_CAMERA_DEVICE = 1;
-
+        public static final int ERROR_CAMERA_DEVICE = 1;
         /**
          * Camera has encountered a service-level error
          * Matches CameraDevice.StateCallback#ERROR_CAMERA_SERVICE
          */
-        static final int ERROR_CAMERA_SERVICE = 2;
-
+        public static final int ERROR_CAMERA_SERVICE = 2;
         /**
          * Camera has encountered an error processing a single request.
          */
-        static final int ERROR_CAMERA_REQUEST = 3;
-
+        public static final int ERROR_CAMERA_REQUEST = 3;
         /**
          * Camera has encountered an error producing metadata for a single capture
          */
-        static final int ERROR_CAMERA_RESULT = 4;
-
+        public static final int ERROR_CAMERA_RESULT = 4;
         /**
          * Camera has encountered an error producing an image buffer for a single capture
          */
-        static final int ERROR_CAMERA_BUFFER = 5;
+        public static final int ERROR_CAMERA_BUFFER = 5;
 
         @Override
         public IBinder asBinder() {
@@ -1187,9 +1180,9 @@
         public void onDeviceError(final int errorCode, CaptureResultExtras resultExtras) {
             if (DEBUG) {
                 Log.d(TAG, String.format(
-                    "Device error received, code %d, frame number %d, request ID %d, subseq ID %d",
-                    errorCode, resultExtras.getFrameNumber(), resultExtras.getRequestId(),
-                    resultExtras.getSubsequenceId()));
+                        "Device error received, code %d, frame number %d, request ID %d, subseq ID %d",
+                        errorCode, resultExtras.getFrameNumber(), resultExtras.getRequestId(),
+                        resultExtras.getSubsequenceId()));
             }
 
             synchronized(mInterfaceLock) {
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
index 2fa9d85..6aab53c 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
@@ -49,6 +49,9 @@
     private static final int STATE_IDLE = 3;
     private static final int STATE_CAPTURING = 4;
 
+    private static final String[] sStateNames = { "ERROR", "UNCONFIGURED", "CONFIGURING", "IDLE",
+            "CAPTURING"};
+
     private int mCurrentState = STATE_UNCONFIGURED;
     private int mCurrentError = CameraBinderDecorator.NO_ERROR;
 
@@ -57,6 +60,11 @@
     private Handler mCurrentHandler = null;
     private CameraDeviceStateListener mCurrentListener = null;
 
+    /**
+     * Error code used by {@link #setCaptureStart} and {@link #setCaptureResult} to indicate that no
+     * error has occurred.
+     */
+    public static final int NO_CAPTURE_ERROR = -1;
 
     /**
      * CameraDeviceStateListener callbacks to be called after state transitions.
@@ -126,11 +134,15 @@
      *
      * @param request A {@link RequestHolder} containing the request for the current capture.
      * @param timestamp The timestamp of the capture start in nanoseconds.
+     * @param captureError Report a recoverable error for a single request using a valid
+     *                     error code for {@code ICameraDeviceCallbacks}, or
+     *                     {@link #NO_CAPTURE_ERROR}
      * @return {@link CameraBinderDecorator#NO_ERROR}, or an error if one has occurred.
      */
-    public synchronized int setCaptureStart(final RequestHolder request, long timestamp) {
+    public synchronized int setCaptureStart(final RequestHolder request, long timestamp,
+                                            int captureError) {
         mCurrentRequest = request;
-        doStateTransition(STATE_CAPTURING, timestamp);
+        doStateTransition(STATE_CAPTURING, timestamp, captureError);
         return mCurrentError;
     }
 
@@ -144,12 +156,16 @@
      * the {@code ERROR} state,
      * </p>
      *
-     * @param request the {@link RequestHolder} request that created this result.
-     * @param result the {@link CameraMetadataNative} result to set.
+     * @param request The {@link RequestHolder} request that created this result.
+     * @param result The {@link CameraMetadataNative} result to set.
+     * @param captureError Report a recoverable error for a single buffer or result using a valid
+     *                     error code for {@code ICameraDeviceCallbacks}, or
+     *                     {@link #NO_CAPTURE_ERROR}.
      * @return {@link CameraBinderDecorator#NO_ERROR}, or an error if one has occurred.
      */
     public synchronized int setCaptureResult(final RequestHolder request,
-                                             final CameraMetadataNative result) {
+                                             final CameraMetadataNative result,
+                                             final int captureError) {
         if (mCurrentState != STATE_CAPTURING) {
             Log.e(TAG, "Cannot receive result while in state: " + mCurrentState);
             mCurrentError = CameraBinderDecorator.INVALID_OPERATION;
@@ -158,12 +174,21 @@
         }
 
         if (mCurrentHandler != null && mCurrentListener != null) {
-            mCurrentHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mCurrentListener.onCaptureResult(result, request);
-                }
-            });
+            if (captureError != NO_CAPTURE_ERROR) {
+                mCurrentHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        mCurrentListener.onError(captureError, request);
+                    }
+                });
+            } else {
+                mCurrentHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        mCurrentListener.onCaptureResult(result, request);
+                    }
+                });
+            }
         }
         return mCurrentError;
     }
@@ -181,14 +206,16 @@
     }
 
     private void doStateTransition(int newState) {
-        doStateTransition(newState, /*timestamp*/0);
+        doStateTransition(newState, /*timestamp*/0, CameraBinderDecorator.NO_ERROR);
     }
 
-    private void doStateTransition(int newState, final long timestamp) {
-        if (DEBUG) {
-            if (newState != mCurrentState) {
-                Log.d(TAG, "Transitioning to state " + newState);
+    private void doStateTransition(int newState, final long timestamp, final int error) {
+        if (newState != mCurrentState) {
+            String stateName = "UNKNOWN";
+            if (newState >= 0 && newState < sStateNames.length) {
+                stateName = sStateNames[newState];
             }
+            Log.i(TAG, "Legacy camera service transitioning to state " + stateName);
         }
         switch(newState) {
             case STATE_ERROR:
@@ -251,13 +278,23 @@
                     doStateTransition(STATE_ERROR);
                     break;
                 }
+
                 if (mCurrentHandler != null && mCurrentListener != null) {
-                    mCurrentHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            mCurrentListener.onCaptureStarted(mCurrentRequest, timestamp);
-                        }
-                    });
+                    if (error != NO_CAPTURE_ERROR) {
+                        mCurrentHandler.post(new Runnable() {
+                            @Override
+                            public void run() {
+                                mCurrentListener.onError(error, mCurrentRequest);
+                            }
+                        });
+                    } else {
+                        mCurrentHandler.post(new Runnable() {
+                            @Override
+                            public void run() {
+                                mCurrentListener.onCaptureStarted(mCurrentRequest, timestamp);
+                            }
+                        });
+                    }
                 }
                 mCurrentState = STATE_CAPTURING;
                 break;
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index 410934e..4aa330d 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -341,6 +341,10 @@
             Log.d(TAG, "disconnect called.");
         }
 
+        if (mLegacyDevice.isClosed()) {
+            Log.w(TAG, "Cannot disconnect, device has already been closed.");
+        }
+
         try {
             mLegacyDevice.close();
         } finally {
@@ -355,6 +359,11 @@
         if (DEBUG) {
             Log.d(TAG, "submitRequest called.");
         }
+        if (mLegacyDevice.isClosed()) {
+            Log.e(TAG, "Cannot submit request, device has been closed.");
+            return CameraBinderDecorator.ENODEV;
+        }
+
         synchronized(mConfigureLock) {
             if (mConfiguring) {
                 Log.e(TAG, "Cannot submit request, configuration change in progress.");
@@ -370,6 +379,11 @@
         if (DEBUG) {
             Log.d(TAG, "submitRequestList called.");
         }
+        if (mLegacyDevice.isClosed()) {
+            Log.e(TAG, "Cannot submit request list, device has been closed.");
+            return CameraBinderDecorator.ENODEV;
+        }
+
         synchronized(mConfigureLock) {
             if (mConfiguring) {
                 Log.e(TAG, "Cannot submit request, configuration change in progress.");
@@ -384,6 +398,11 @@
         if (DEBUG) {
             Log.d(TAG, "cancelRequest called.");
         }
+        if (mLegacyDevice.isClosed()) {
+            Log.e(TAG, "Cannot cancel request, device has been closed.");
+            return CameraBinderDecorator.ENODEV;
+        }
+
         synchronized(mConfigureLock) {
             if (mConfiguring) {
                 Log.e(TAG, "Cannot cancel request, configuration change in progress.");
@@ -400,6 +419,11 @@
         if (DEBUG) {
             Log.d(TAG, "beginConfigure called.");
         }
+        if (mLegacyDevice.isClosed()) {
+            Log.e(TAG, "Cannot begin configure, device has been closed.");
+            return CameraBinderDecorator.ENODEV;
+        }
+
         synchronized(mConfigureLock) {
             if (mConfiguring) {
                 Log.e(TAG, "Cannot begin configure, configuration change already in progress.");
@@ -415,6 +439,11 @@
         if (DEBUG) {
             Log.d(TAG, "endConfigure called.");
         }
+        if (mLegacyDevice.isClosed()) {
+            Log.e(TAG, "Cannot end configure, device has been closed.");
+            return CameraBinderDecorator.ENODEV;
+        }
+
         ArrayList<Surface> surfaces = null;
         synchronized(mConfigureLock) {
             if (!mConfiguring) {
@@ -438,6 +467,11 @@
         if (DEBUG) {
             Log.d(TAG, "deleteStream called.");
         }
+        if (mLegacyDevice.isClosed()) {
+            Log.e(TAG, "Cannot delete stream, device has been closed.");
+            return CameraBinderDecorator.ENODEV;
+        }
+
         synchronized(mConfigureLock) {
             if (!mConfiguring) {
                 Log.e(TAG, "Cannot delete stream, beginConfigure hasn't been called yet.");
@@ -458,6 +492,11 @@
         if (DEBUG) {
             Log.d(TAG, "createStream called.");
         }
+        if (mLegacyDevice.isClosed()) {
+            Log.e(TAG, "Cannot create stream, device has been closed.");
+            return CameraBinderDecorator.ENODEV;
+        }
+
         synchronized(mConfigureLock) {
             if (!mConfiguring) {
                 Log.e(TAG, "Cannot create stream, beginConfigure hasn't been called yet.");
@@ -474,6 +513,10 @@
         if (DEBUG) {
             Log.d(TAG, "createDefaultRequest called.");
         }
+        if (mLegacyDevice.isClosed()) {
+            Log.e(TAG, "Cannot create default request, device has been closed.");
+            return CameraBinderDecorator.ENODEV;
+        }
 
         CameraMetadataNative template;
         try {
@@ -503,6 +546,11 @@
         if (DEBUG) {
             Log.d(TAG, "waitUntilIdle called.");
         }
+        if (mLegacyDevice.isClosed()) {
+            Log.e(TAG, "Cannot wait until idle, device has been closed.");
+            return CameraBinderDecorator.ENODEV;
+        }
+
         synchronized(mConfigureLock) {
             if (mConfiguring) {
                 Log.e(TAG, "Cannot wait until idle, configuration change in progress.");
@@ -518,13 +566,21 @@
         if (DEBUG) {
             Log.d(TAG, "flush called.");
         }
+        if (mLegacyDevice.isClosed()) {
+            Log.e(TAG, "Cannot flush, device has been closed.");
+            return CameraBinderDecorator.ENODEV;
+        }
+
         synchronized(mConfigureLock) {
             if (mConfiguring) {
                 Log.e(TAG, "Cannot flush, configuration change in progress.");
                 return CameraBinderDecorator.INVALID_OPERATION;
             }
         }
-        // TODO: implement flush.
+        long lastFrame = mLegacyDevice.flush();
+        if (lastFrameNumber != null) {
+            lastFrameNumber.setNumber(lastFrame);
+        }
         return CameraBinderDecorator.NO_ERROR;
     }
 
diff --git a/core/java/android/hardware/camera2/legacy/CaptureCollector.java b/core/java/android/hardware/camera2/legacy/CaptureCollector.java
index 307e466..8404e86 100644
--- a/core/java/android/hardware/camera2/legacy/CaptureCollector.java
+++ b/core/java/android/hardware/camera2/legacy/CaptureCollector.java
@@ -15,12 +15,14 @@
  */
 package android.hardware.camera2.legacy;
 
+import android.hardware.camera2.impl.CameraDeviceImpl;
 import android.util.Log;
 import android.util.MutableLong;
 import android.util.Pair;
 
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.TreeSet;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.ReentrantLock;
@@ -44,7 +46,7 @@
 
     private static final int MAX_JPEGS_IN_FLIGHT = 1;
 
-    private class CaptureHolder {
+    private class CaptureHolder implements Comparable<CaptureHolder>{
         private final RequestHolder mRequest;
         private final LegacyRequest mLegacy;
         public final boolean needsJpeg;
@@ -53,6 +55,10 @@
         private long mTimestamp = 0;
         private int mReceivedFlags = 0;
         private boolean mHasStarted = false;
+        private boolean mFailedJpeg = false;
+        private boolean mFailedPreview = false;
+        private boolean mCompleted = false;
+        private boolean mPreviewCompleted = false;
 
         public CaptureHolder(RequestHolder request, LegacyRequest legacyHolder) {
             mRequest = request;
@@ -74,11 +80,43 @@
         }
 
         public void tryComplete() {
-            if (isCompleted()) {
-                if (needsPreview && isPreviewCompleted()) {
-                    CaptureCollector.this.onPreviewCompleted();
+            if (!mPreviewCompleted && needsPreview && isPreviewCompleted()) {
+                CaptureCollector.this.onPreviewCompleted();
+                mPreviewCompleted = true;
+            }
+
+            if (isCompleted() && !mCompleted) {
+                if (mFailedPreview || mFailedJpeg) {
+                    if (!mHasStarted) {
+                        // Send a request error if the capture has not yet started.
+                        mRequest.failRequest();
+                        CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp,
+                                CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_REQUEST);
+                    } else {
+                        // Send buffer dropped errors for each pending buffer if the request has
+                        // started.
+                        if (mFailedPreview) {
+                            Log.w(TAG, "Preview buffers dropped for request: " +
+                                    mRequest.getRequestId());
+                            for (int i = 0; i < mRequest.numPreviewTargets(); i++) {
+                                CaptureCollector.this.mDeviceState.setCaptureResult(mRequest,
+                                    /*result*/null,
+                                        CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_BUFFER);
+                            }
+                        }
+                        if (mFailedJpeg) {
+                            Log.w(TAG, "Jpeg buffers dropped for request: " +
+                                    mRequest.getRequestId());
+                            for (int i = 0; i < mRequest.numJpegTargets(); i++) {
+                                CaptureCollector.this.mDeviceState.setCaptureResult(mRequest,
+                                    /*result*/null,
+                                        CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_BUFFER);
+                            }
+                        }
+                    }
                 }
-                CaptureCollector.this.onRequestCompleted(this);
+                CaptureCollector.this.onRequestCompleted(CaptureHolder.this);
+                mCompleted = true;
             }
         }
 
@@ -103,7 +141,8 @@
 
             if (!mHasStarted) {
                 mHasStarted = true;
-                CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp);
+                CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp,
+                        CameraDeviceState.NO_CAPTURE_ERROR);
             }
 
             tryComplete();
@@ -126,6 +165,20 @@
             tryComplete();
         }
 
+        public void setJpegFailed() {
+            if (DEBUG) {
+                Log.d(TAG, "setJpegFailed - called for request " + mRequest.getRequestId());
+            }
+            if (!needsJpeg || isJpegCompleted()) {
+                return;
+            }
+            mFailedJpeg = true;
+
+            mReceivedFlags |= FLAG_RECEIVED_JPEG;
+            mReceivedFlags |= FLAG_RECEIVED_JPEG_TS;
+            tryComplete();
+        }
+
         public void setPreviewTimestamp(long timestamp) {
             if (DEBUG) {
                 Log.d(TAG, "setPreviewTimestamp - called for request " + mRequest.getRequestId());
@@ -148,7 +201,8 @@
             if (!needsJpeg) {
                 if (!mHasStarted) {
                     mHasStarted = true;
-                    CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp);
+                    CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp,
+                            CameraDeviceState.NO_CAPTURE_ERROR);
                 }
             }
 
@@ -171,8 +225,37 @@
             mReceivedFlags |= FLAG_RECEIVED_PREVIEW;
             tryComplete();
         }
+
+        public void setPreviewFailed() {
+            if (DEBUG) {
+                Log.d(TAG, "setPreviewFailed - called for request " + mRequest.getRequestId());
+            }
+            if (!needsPreview || isPreviewCompleted()) {
+                return;
+            }
+            mFailedPreview = true;
+
+            mReceivedFlags |= FLAG_RECEIVED_PREVIEW;
+            mReceivedFlags |= FLAG_RECEIVED_PREVIEW_TS;
+            tryComplete();
+        }
+
+        // Comparison and equals based on frame number.
+        @Override
+        public int compareTo(CaptureHolder captureHolder) {
+            return (mRequest.getFrameNumber() > captureHolder.mRequest.getFrameNumber()) ? 1 :
+                    ((mRequest.getFrameNumber() == captureHolder.mRequest.getFrameNumber()) ? 0 :
+                            -1);
+        }
+
+        // Comparison and equals based on frame number.
+        @Override
+        public boolean equals(Object o) {
+            return o instanceof CaptureHolder && compareTo((CaptureHolder) o) == 0;
+        }
     }
 
+    private final TreeSet<CaptureHolder> mActiveRequests;
     private final ArrayDeque<CaptureHolder> mJpegCaptureQueue;
     private final ArrayDeque<CaptureHolder> mJpegProduceQueue;
     private final ArrayDeque<CaptureHolder> mPreviewCaptureQueue;
@@ -200,6 +283,7 @@
         mJpegProduceQueue = new ArrayDeque<>(MAX_JPEGS_IN_FLIGHT);
         mPreviewCaptureQueue = new ArrayDeque<>(mMaxInFlight);
         mPreviewProduceQueue = new ArrayDeque<>(mMaxInFlight);
+        mActiveRequests = new TreeSet<>();
         mIsEmpty = mLock.newCondition();
         mNotFull = mLock.newCondition();
         mPreviewsEmpty = mLock.newCondition();
@@ -263,7 +347,7 @@
                 mPreviewProduceQueue.add(h);
                 mInFlightPreviews++;
             }
-
+            mActiveRequests.add(h);
 
             mInFlight++;
             return true;
@@ -440,7 +524,9 @@
         try {
             CaptureHolder h = mPreviewCaptureQueue.poll();
             if (h == null) {
-                Log.w(TAG, "previewCaptured called with no preview request on queue!");
+                if (DEBUG) {
+                    Log.d(TAG, "previewCaptured called with no preview request on queue!");
+                }
                 return null;
             }
             h.setPreviewTimestamp(timestamp);
@@ -471,6 +557,81 @@
         }
     }
 
+    /**
+     * Called to alert the {@link CaptureCollector} that the next pending preview capture has failed.
+     */
+    public void failNextPreview() {
+        final ReentrantLock lock = this.mLock;
+        lock.lock();
+        try {
+            CaptureHolder h1 = mPreviewCaptureQueue.peek();
+            CaptureHolder h2 = mPreviewProduceQueue.peek();
+
+            // Find the request with the lowest frame number.
+            CaptureHolder h = (h1 == null) ? h2 :
+                              ((h2 == null) ? h1 :
+                              ((h1.compareTo(h2) <= 0) ? h1 :
+                              h2));
+
+            if (h != null) {
+                mPreviewCaptureQueue.remove(h);
+                mPreviewProduceQueue.remove(h);
+                mActiveRequests.remove(h);
+                h.setPreviewFailed();
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Called to alert the {@link CaptureCollector} that the next pending jpeg capture has failed.
+     */
+    public void failNextJpeg() {
+        final ReentrantLock lock = this.mLock;
+        lock.lock();
+        try {
+            CaptureHolder h1 = mJpegCaptureQueue.peek();
+            CaptureHolder h2 = mJpegProduceQueue.peek();
+
+            // Find the request with the lowest frame number.
+            CaptureHolder h = (h1 == null) ? h2 :
+                              ((h2 == null) ? h1 :
+                              ((h1.compareTo(h2) <= 0) ? h1 :
+                              h2));
+
+            if (h != null) {
+                mJpegCaptureQueue.remove(h);
+                mJpegProduceQueue.remove(h);
+                mActiveRequests.remove(h);
+                h.setJpegFailed();
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Called to alert the {@link CaptureCollector} all pending captures have failed.
+     */
+    public void failAll() {
+        final ReentrantLock lock = this.mLock;
+        lock.lock();
+        try {
+            CaptureHolder h;
+            while ((h = mActiveRequests.pollFirst()) != null) {
+                h.setPreviewFailed();
+                h.setJpegFailed();
+            }
+            mPreviewCaptureQueue.clear();
+            mPreviewProduceQueue.clear();
+            mJpegCaptureQueue.clear();
+            mJpegProduceQueue.clear();
+        } finally {
+            lock.unlock();
+        }
+    }
+
     private void onPreviewCompleted() {
         mInFlightPreviews--;
         if (mInFlightPreviews < 0) {
@@ -496,6 +657,7 @@
         }
 
         mCompletedRequests.add(capture);
+        mActiveRequests.remove(capture);
 
         mNotFull.signalAll();
         if (mInFlight == 0) {
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index ffc55f1..9143152 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -23,6 +23,9 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.impl.CaptureResultExtras;
 import android.hardware.camera2.ICameraDeviceCallbacks;
+import android.hardware.camera2.params.StreamConfiguration;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.hardware.camera2.utils.ArrayUtils;
 import android.hardware.camera2.utils.LongParcelable;
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.utils.CameraRuntimeException;
@@ -35,6 +38,7 @@
 import android.view.Surface;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 
@@ -56,11 +60,13 @@
     public static final String DEBUG_PROP = "HAL1ShimLogging";
     private final String TAG;
 
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
     private final int mCameraId;
+    private final CameraCharacteristics mStaticCharacteristics;
     private final ICameraDeviceCallbacks mDeviceCallbacks;
     private final CameraDeviceState mDeviceState = new CameraDeviceState();
     private List<Surface> mConfiguredSurfaces;
+    private boolean mClosed = false;
 
     private final ConditionVariable mIdle = new ConditionVariable(/*open*/true);
 
@@ -87,14 +93,15 @@
     private final CameraDeviceState.CameraDeviceStateListener mStateListener =
             new CameraDeviceState.CameraDeviceStateListener() {
         @Override
-        public void onError(final int errorCode, RequestHolder holder) {
+        public void onError(final int errorCode, final RequestHolder holder) {
             mIdle.open();
             final CaptureResultExtras extras = getExtrasFromRequest(holder);
             mResultHandler.post(new Runnable() {
                 @Override
                 public void run() {
                     if (DEBUG) {
-                        Log.d(TAG, "doing onError callback.");
+                        Log.d(TAG, "doing onError callback for request " + holder.getRequestId() +
+                                ", with error code " + errorCode);
                     }
                     try {
                         mDeviceCallbacks.onDeviceError(errorCode, extras);
@@ -135,14 +142,15 @@
         }
 
         @Override
-        public void onCaptureStarted(RequestHolder holder, final long timestamp) {
+        public void onCaptureStarted(final RequestHolder holder, final long timestamp) {
             final CaptureResultExtras extras = getExtrasFromRequest(holder);
 
             mResultHandler.post(new Runnable() {
                 @Override
                 public void run() {
                     if (DEBUG) {
-                        Log.d(TAG, "doing onCaptureStarted callback.");
+                        Log.d(TAG, "doing onCaptureStarted callback for request " +
+                                holder.getRequestId());
                     }
                     try {
                         mDeviceCallbacks.onCaptureStarted(extras, timestamp);
@@ -155,14 +163,15 @@
         }
 
         @Override
-        public void onCaptureResult(final CameraMetadataNative result, RequestHolder holder) {
+        public void onCaptureResult(final CameraMetadataNative result, final RequestHolder holder) {
             final CaptureResultExtras extras = getExtrasFromRequest(holder);
 
             mResultHandler.post(new Runnable() {
                 @Override
                 public void run() {
                     if (DEBUG) {
-                        Log.d(TAG, "doing onCaptureResult callback.");
+                        Log.d(TAG, "doing onCaptureResult callback for request " +
+                                holder.getRequestId());
                     }
                     try {
                         mDeviceCallbacks.onResultReceived(result, extras);
@@ -216,6 +225,7 @@
         mCallbackHandlerThread.start();
         mCallbackHandler = new Handler(mCallbackHandlerThread.getLooper());
         mDeviceState.setCameraDeviceCallbacks(mCallbackHandler, mStateListener);
+        mStaticCharacteristics = characteristics;
         mRequestThreadManager =
                 new RequestThreadManager(cameraId, camera, characteristics, mDeviceState);
         mRequestThreadManager.start();
@@ -239,6 +249,42 @@
                     Log.e(TAG, "configureOutputs - null outputs are not allowed");
                     return BAD_VALUE;
                 }
+                StreamConfigurationMap streamConfigurations = mStaticCharacteristics.
+                        get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+
+                // Validate surface size and format.
+                try {
+                    Size s = getSurfaceSize(output);
+                    int surfaceType = detectSurfaceType(output);
+                    Size[] sizes = streamConfigurations.getOutputSizes(surfaceType);
+
+                    if (sizes == null) {
+                        // WAR: Override default format to IMPLEMENTATION_DEFINED for b/9487482
+                        if ((surfaceType >= LegacyMetadataMapper.HAL_PIXEL_FORMAT_RGBA_8888 &&
+                            surfaceType <= LegacyMetadataMapper.HAL_PIXEL_FORMAT_BGRA_8888)) {
+
+                            // YUV_420_888 is always present in LEGACY for all IMPLEMENTATION_DEFINED
+                            // output sizes, and is publicly visible in the API (i.e.
+                            // {@code #getOutputSizes} works here).
+                            sizes = streamConfigurations.getOutputSizes(ImageFormat.YUV_420_888);
+                        } else if (surfaceType == LegacyMetadataMapper.HAL_PIXEL_FORMAT_BLOB) {
+                            sizes = streamConfigurations.getOutputSizes(ImageFormat.JPEG);
+                        }
+                    }
+
+                    if (!ArrayUtils.contains(sizes, s)) {
+                        String reason = (sizes == null) ? "format is invalid." :
+                                ("size not in valid set: " + Arrays.toString(sizes));
+                        Log.e(TAG, String.format("Surface with size (w=%d, h=%d) and format 0x%x is"
+                                + " not valid, %s", s.getWidth(), s.getHeight(), surfaceType,
+                                reason));
+                        return BAD_VALUE;
+                    }
+                } catch (BufferQueueAbandonedException e) {
+                    Log.e(TAG, "Surface bufferqueue is abandoned, cannot configure as output: ", e);
+                    return BAD_VALUE;
+                }
+
             }
         }
 
@@ -248,7 +294,6 @@
             error = mDeviceState.setIdle();
         }
 
-        // TODO: May also want to check the surfaces more deeply (e.g. state, formats, sizes..)
         if (error == NO_ERROR) {
             mConfiguredSurfaces = outputs != null ? new ArrayList<>(outputs) : null;
         }
@@ -342,6 +387,24 @@
         mIdle.block();
     }
 
+    /**
+     * Flush any pending requests.
+     *
+     * @return the last frame number.
+     */
+    public long flush() {
+        long lastFrame = mRequestThreadManager.flush();
+        waitUntilIdle();
+        return lastFrame;
+    }
+
+    /**
+     * Return {@code true} if the device has been closed.
+     */
+    public boolean isClosed() {
+        return mClosed;
+    }
+
     @Override
     public void close() {
         mRequestThreadManager.quit();
@@ -362,7 +425,7 @@
                     mResultThread.getName(), mResultThread.getId()));
         }
 
-        // TODO: throw IllegalStateException in every method after close has been called
+        mClosed = true;
     }
 
     @Override
diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
index c36b63a..907d2ae 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
@@ -17,6 +17,7 @@
 package android.hardware.camera2.legacy;
 
 import android.graphics.ImageFormat;
+import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.hardware.Camera;
 import android.hardware.Camera.CameraInfo;
@@ -61,8 +62,10 @@
     private static final long NS_PER_MS = 1000000;
 
     // from graphics.h
-    private static final int HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED = 0x22;
-    private static final int HAL_PIXEL_FORMAT_BLOB = 0x21;
+    public static final int HAL_PIXEL_FORMAT_RGBA_8888 = PixelFormat.RGBA_8888;
+    public static final int HAL_PIXEL_FORMAT_BGRA_8888 = 0x5;
+    public static final int HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED = 0x22;
+    public static final int HAL_PIXEL_FORMAT_BLOB = 0x21;
 
     // for metadata
     private static final float LENS_INFO_MINIMUM_FOCUS_DISTANCE_FIXED_FOCUS = 0.0f;
@@ -1170,7 +1173,7 @@
             Rect activeArray = c.get(SENSOR_INFO_ACTIVE_ARRAY_SIZE);
             MeteringRectangle[] activeRegions =  new MeteringRectangle[] {
                     new MeteringRectangle(/*x*/0, /*y*/0, /*width*/activeArray.width() - 1,
-                    /*height*/activeArray.height() - 1,/*weight*/1)};
+                    /*height*/activeArray.height() - 1,/*weight*/0)};
             m.set(CaptureRequest.CONTROL_AE_REGIONS, activeRegions);
             m.set(CaptureRequest.CONTROL_AWB_REGIONS, activeRegions);
             m.set(CaptureRequest.CONTROL_AF_REGIONS, activeRegions);
@@ -1275,6 +1278,11 @@
         m.set(CaptureRequest.FLASH_MODE, FLASH_MODE_OFF);
 
         /*
+         * noiseReduction.*
+         */
+        m.set(CaptureRequest.NOISE_REDUCTION_MODE, NOISE_REDUCTION_MODE_FAST);
+
+        /*
          * lens.*
          */
 
diff --git a/core/java/android/hardware/camera2/legacy/RequestHolder.java b/core/java/android/hardware/camera2/legacy/RequestHolder.java
index 9f27093..69c140b 100644
--- a/core/java/android/hardware/camera2/legacy/RequestHolder.java
+++ b/core/java/android/hardware/camera2/legacy/RequestHolder.java
@@ -26,7 +26,9 @@
 import static com.android.internal.util.Preconditions.*;
 
 /**
- * Immutable container for a single capture request and associated information.
+ * Semi-immutable container for a single capture request and associated information,
+ * the only mutable characteristic of this container is whether or not is has been
+ * marked as "failed" using {@code #failRequest}.
  */
 public class RequestHolder {
     private static final String TAG = "RequestHolder";
@@ -36,8 +38,9 @@
     private final int mRequestId;
     private final int mSubsequeceId;
     private final long mFrameNumber;
-    private final boolean mHasJpegTargets;
-    private final boolean mHasPreviewTargets;
+    private final int mNumJpegTargets;
+    private final int mNumPreviewTargets;
+    private volatile boolean mFailed = false;
 
     /**
      * Returns true if the given surface requires jpeg buffers.
@@ -71,36 +74,37 @@
     }
 
     /**
-     * Returns true if any of the surfaces targeted by the contained request require jpeg buffers.
+     * Returns the number of surfaces targeted by the request that require jpeg buffers.
      */
-    private static boolean requestContainsJpegTargets(CaptureRequest request) {
+    private static int numJpegTargets(CaptureRequest request) {
+        int count = 0;
         for (Surface s : request.getTargets()) {
             try {
                 if (jpegType(s)) {
-                    return true;
+                    ++count;
                 }
             } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
                 Log.w(TAG, "Surface abandoned, skipping...", e);
             }
         }
-        return false;
+        return count;
     }
 
     /**
-     * Returns true if any of the surfaces targeted by the contained request require a
-     * non-jpeg buffer type.
+     * Returns the number of surfaces targeted by the request that require non-jpeg buffers.
      */
-    private static boolean requestContainsPreviewTargets(CaptureRequest request) {
+    private static int numPreviewTargets(CaptureRequest request) {
+        int count = 0;
         for (Surface s : request.getTargets()) {
             try {
                 if (previewType(s)) {
-                    return true;
+                    ++count;
                 }
             } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
                 Log.w(TAG, "Surface abandoned, skipping...", e);
             }
         }
-        return false;
+        return count;
     }
 
     /**
@@ -115,8 +119,8 @@
         private final int mSubsequenceId;
         private final CaptureRequest mRequest;
         private final boolean mRepeating;
-        private final boolean mHasJpegTargets;
-        private final boolean mHasPreviewTargets;
+        private final int mNumJpegTargets;
+        private final int mNumPreviewTargets;
 
         /**
          * Construct a new {@link Builder} to generate {@link RequestHolder} objects.
@@ -134,8 +138,8 @@
             mSubsequenceId = subsequenceId;
             mRequest = request;
             mRepeating = repeating;
-            mHasJpegTargets = requestContainsJpegTargets(mRequest);
-            mHasPreviewTargets = requestContainsPreviewTargets(mRequest);
+            mNumJpegTargets = numJpegTargets(mRequest);
+            mNumPreviewTargets = numPreviewTargets(mRequest);
         }
 
         /**
@@ -147,20 +151,20 @@
          */
         public RequestHolder build(long frameNumber) {
             return new RequestHolder(mRequestId, mSubsequenceId, mRequest, mRepeating, frameNumber,
-                    mHasJpegTargets, mHasPreviewTargets);
+                    mNumJpegTargets, mNumPreviewTargets);
         }
     }
 
     private RequestHolder(int requestId, int subsequenceId, CaptureRequest request,
-                          boolean repeating, long frameNumber, boolean hasJpegTargets,
-                          boolean hasPreviewTargets) {
+                          boolean repeating, long frameNumber, int numJpegTargets,
+                          int numPreviewTargets) {
         mRepeating = repeating;
         mRequest = request;
         mRequestId = requestId;
         mSubsequeceId = subsequenceId;
         mFrameNumber = frameNumber;
-        mHasJpegTargets = hasJpegTargets;
-        mHasPreviewTargets = hasPreviewTargets;
+        mNumJpegTargets = numJpegTargets;
+        mNumPreviewTargets = numPreviewTargets;
     }
 
     /**
@@ -209,7 +213,7 @@
      * Returns true if any of the surfaces targeted by the contained request require jpeg buffers.
      */
     public boolean hasJpegTargets() {
-        return mHasJpegTargets;
+        return mNumJpegTargets > 0;
     }
 
     /**
@@ -217,7 +221,36 @@
      * non-jpeg buffer type.
      */
     public boolean hasPreviewTargets(){
-        return mHasPreviewTargets;
+        return mNumPreviewTargets > 0;
+    }
+
+    /**
+     * Return the number of jpeg-type surfaces targeted by this request.
+     */
+    public int numJpegTargets() {
+        return mNumJpegTargets;
+    }
+
+    /**
+     * Return the number of non-jpeg-type surfaces targeted by this request.
+     */
+    public int numPreviewTargets() {
+        return mNumPreviewTargets;
+    }
+
+    /**
+     * Mark this request as failed.
+     */
+    public void failRequest() {
+        Log.w(TAG, "Capture failed for request: " + getRequestId());
+        mFailed = true;
+    }
+
+    /**
+     * Return {@code true} if this request failed.
+     */
+    public boolean requestFailed() {
+        return mFailed;
     }
 
 }
diff --git a/core/java/android/hardware/camera2/legacy/RequestQueue.java b/core/java/android/hardware/camera2/legacy/RequestQueue.java
index 7820648..7598f99 100644
--- a/core/java/android/hardware/camera2/legacy/RequestQueue.java
+++ b/core/java/android/hardware/camera2/legacy/RequestQueue.java
@@ -80,6 +80,7 @@
             ret = (mCurrentRepeatingFrameNumber == INVALID_FRAME) ? INVALID_FRAME :
                     mCurrentRepeatingFrameNumber - 1;
             mCurrentRepeatingFrameNumber = INVALID_FRAME;
+            Log.i(TAG, "Repeating capture request cancelled.");
         } else {
             Log.e(TAG, "cancel failed: no repeating request exists for request id: " + requestId);
         }
@@ -87,6 +88,20 @@
     }
 
     /**
+     * Cancel a repeating request.
+     *
+     * @return the last frame to be returned from the HAL for the given repeating request, or
+     *          {@code INVALID_FRAME} if none exists.
+     */
+    public synchronized long stopRepeating() {
+        if (mRepeatingRequest == null) {
+            Log.e(TAG, "cancel failed: no repeating request exists.");
+            return INVALID_FRAME;
+        }
+        return stopRepeating(mRepeatingRequest.getRequestId());
+    }
+
+    /**
      * Add a the given burst to the queue.
      *
      * <p>If the burst is repeating, replace the current repeating burst.</p>
@@ -105,6 +120,7 @@
         BurstHolder burst = new BurstHolder(requestId, repeating, requests);
         long ret = INVALID_FRAME;
         if (burst.isRepeating()) {
+            Log.i(TAG, "Repeating capture request set.");
             if (mRepeatingRequest != null) {
                 ret = (mCurrentRepeatingFrameNumber == INVALID_FRAME) ? INVALID_FRAME :
                         mCurrentRepeatingFrameNumber - 1;
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index e6da670..87ee1ee 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -20,6 +20,7 @@
 import android.hardware.Camera;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.impl.CameraDeviceImpl;
 import android.hardware.camera2.utils.LongParcelable;
 import android.hardware.camera2.utils.SizeAreaComparator;
 import android.hardware.camera2.impl.CameraMetadataNative;
@@ -33,7 +34,6 @@
 import android.util.Size;
 import android.view.Surface;
 
-import java.io.IOError;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -284,10 +284,9 @@
         startPreview();
     }
 
-    private void configureOutputs(Collection<Surface> outputs) throws IOException {
+    private void configureOutputs(Collection<Surface> outputs) {
         if (DEBUG) {
             String outputsStr = outputs == null ? "null" : (outputs.size() + " surfaces");
-
             Log.d(TAG, "configureOutputs with " + outputsStr);
         }
 
@@ -297,7 +296,11 @@
          * using a different one; this also reduces the likelihood of getting into a deadlock
          * when disconnecting from the old previous texture at a later time.
          */
-        mCamera.setPreviewTexture(/*surfaceTexture*/null);
+        try {
+            mCamera.setPreviewTexture(/*surfaceTexture*/null);
+        } catch (IOException e) {
+            Log.w(TAG, "Failed to clear prior SurfaceTexture, may cause GL deadlock: ", e);
+        }
 
         if (mGLThreadManager != null) {
             mGLThreadManager.waitUntilStarted();
@@ -568,26 +571,23 @@
                 case MSG_CONFIGURE_OUTPUTS:
                     ConfigureHolder config = (ConfigureHolder) msg.obj;
                     int sizes = config.surfaces != null ? config.surfaces.size() : 0;
-                    Log.i(TAG, "Configure outputs: " + sizes +
-                            " surfaces configured.");
+                    Log.i(TAG, "Configure outputs: " + sizes + " surfaces configured.");
 
                     try {
                         boolean success = mCaptureCollector.waitForEmpty(JPEG_FRAME_TIMEOUT,
                                 TimeUnit.MILLISECONDS);
                         if (!success) {
                             Log.e(TAG, "Timed out while queueing configure request.");
+                            mCaptureCollector.failAll();
                         }
                     } catch (InterruptedException e) {
-                        // TODO: report error to CameraDevice
                         Log.e(TAG, "Interrupted while waiting for requests to complete.");
+                        mDeviceState.setError(
+                                CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
+                        break;
                     }
 
-                    try {
-                        configureOutputs(config.surfaces);
-                    } catch (IOException e) {
-                        // TODO: report error to CameraDevice
-                        throw new IOError(e);
-                    }
+                    configureOutputs(config.surfaces);
                     config.condition.open();
                     if (DEBUG) {
                         long totalTime = SystemClock.elapsedRealtimeNanos() - startTime;
@@ -599,16 +599,23 @@
 
                     // Get the next burst from the request queue.
                     Pair<BurstHolder, Long> nextBurst = mRequestQueue.getNext();
+
                     if (nextBurst == null) {
+                        // If there are no further requests queued, wait for any currently executing
+                        // requests to complete, then switch to idle state.
                         try {
                             boolean success = mCaptureCollector.waitForEmpty(JPEG_FRAME_TIMEOUT,
                                     TimeUnit.MILLISECONDS);
                             if (!success) {
-                                Log.e(TAG, "Timed out while waiting for empty.");
+                                Log.e(TAG,
+                                        "Timed out while waiting for prior requests to complete.");
+                                mCaptureCollector.failAll();
                             }
                         } catch (InterruptedException e) {
-                            // TODO: report error to CameraDevice
-                            Log.e(TAG, "Interrupted while waiting for requests to complete.");
+                            Log.e(TAG, "Interrupted while waiting for requests to complete: ", e);
+                            mDeviceState.setError(
+                                    CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
+                            break;
                         }
                         mDeviceState.setIdle();
                         break;
@@ -625,27 +632,39 @@
 
                         boolean paramsChanged = false;
 
-                        // Lazily process the rest of the request
+                        // Only update parameters if the request has changed
                         if (mLastRequest == null || mLastRequest.captureRequest != request) {
 
                             // The intermediate buffer is sometimes null, but we always need
-                            // the camera1's configured preview size
+                            // the Camera1 API configured preview size
                             Size previewSize = ParameterUtils.convertSize(mParams.getPreviewSize());
 
-                            LegacyRequest legacyRequest = new LegacyRequest(
-                                    mCharacteristics, request, previewSize,
-                                    mParams); // params are copied
+                            LegacyRequest legacyRequest = new LegacyRequest(mCharacteristics,
+                                    request, previewSize, mParams); // params are copied
 
-                            mLastRequest = legacyRequest;
+
                             // Parameters are mutated as a side-effect
                             LegacyMetadataMapper.convertRequestMetadata(/*inout*/legacyRequest);
 
+                            // If the parameters have changed, set them in the Camera1 API.
                             if (!mParams.same(legacyRequest.parameters)) {
-                                mParams = legacyRequest.parameters;
-                                mCamera.setParameters(mParams);
-
+                                try {
+                                    mCamera.setParameters(legacyRequest.parameters);
+                                } catch (RuntimeException e) {
+                                    // If setting the parameters failed, report a request error to
+                                    // the camera client, and skip any further work for this request
+                                    Log.e(TAG, "Exception while setting camera parameters: ", e);
+                                    holder.failRequest();
+                                    mDeviceState.setCaptureStart(holder, /*timestamp*/0,
+                                            CameraDeviceImpl.CameraDeviceCallbacks.
+                                                    ERROR_CAMERA_REQUEST);
+                                    continue;
+                                }
                                 paramsChanged = true;
+                                mParams = legacyRequest.parameters;
                             }
+
+                            mLastRequest = legacyRequest;
                         }
 
                         try {
@@ -653,19 +672,27 @@
                                     mLastRequest, JPEG_FRAME_TIMEOUT, TimeUnit.MILLISECONDS);
 
                             if (!success) {
+                                // Report a request error if we timed out while queuing this.
                                 Log.e(TAG, "Timed out while queueing capture request.");
+                                holder.failRequest();
+                                mDeviceState.setCaptureStart(holder, /*timestamp*/0,
+                                        CameraDeviceImpl.CameraDeviceCallbacks.
+                                                ERROR_CAMERA_REQUEST);
+                                continue;
                             }
+
                             // Starting the preview needs to happen before enabling
                             // face detection or auto focus
                             if (holder.hasPreviewTargets()) {
                                 doPreviewCapture(holder);
                             }
                             if (holder.hasJpegTargets()) {
-                                success = mCaptureCollector.
-                                        waitForPreviewsEmpty(PREVIEW_FRAME_TIMEOUT *
-                                                MAX_IN_FLIGHT_REQUESTS, TimeUnit.MILLISECONDS);
-                                if (!success) {
-                                    Log.e(TAG, "Timed out waiting for prior requests to complete.");
+                                while(!mCaptureCollector.waitForPreviewsEmpty(PREVIEW_FRAME_TIMEOUT,
+                                        TimeUnit.MILLISECONDS)) {
+                                    // Fail preview requests until the queue is empty.
+                                    Log.e(TAG, "Timed out while waiting for preview requests to " +
+                                            "complete.");
+                                    mCaptureCollector.failNextPreview();
                                 }
                                 mReceivedJpeg.close();
                                 doJpegCapturePrepare(holder);
@@ -686,17 +713,21 @@
                             if (holder.hasJpegTargets()) {
                                 doJpegCapture(holder);
                                 if (!mReceivedJpeg.block(JPEG_FRAME_TIMEOUT)) {
-                                    // TODO: report error to CameraDevice
                                     Log.e(TAG, "Hit timeout for jpeg callback!");
+                                    mCaptureCollector.failNextJpeg();
                                 }
                             }
 
                         } catch (IOException e) {
-                            // TODO: report error to CameraDevice
-                            throw new IOError(e);
+                            Log.e(TAG, "Received device exception: ", e);
+                            mDeviceState.setError(
+                                    CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
+                            break;
                         } catch (InterruptedException e) {
-                            // TODO: report error to CameraDevice
-                            Log.e(TAG, "Interrupted during capture.", e);
+                            Log.e(TAG, "Interrupted during capture: ", e);
+                            mDeviceState.setError(
+                                    CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
+                            break;
                         }
 
                         if (paramsChanged) {
@@ -717,10 +748,13 @@
 
                             if (!success) {
                                 Log.e(TAG, "Timed out while waiting for request to complete.");
+                                mCaptureCollector.failAll();
                             }
                         } catch (InterruptedException e) {
-                         // TODO: report error to CameraDevice
-                            Log.e(TAG, "Interrupted during request completition.", e);
+                            Log.e(TAG, "Interrupted waiting for request completion: ", e);
+                            mDeviceState.setError(
+                                    CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
+                            break;
                         }
 
                         CameraMetadataNative result = mMapper.cachedConvertResultMetadata(
@@ -736,7 +770,10 @@
                         // Update face-related results
                         mFaceDetectMapper.mapResultFaces(result, mLastRequest);
 
-                        mDeviceState.setCaptureResult(holder, result);
+                        if (!holder.requestFailed()) {
+                            mDeviceState.setCaptureResult(holder, result,
+                                    CameraDeviceState.NO_CAPTURE_ERROR);
+                        }
                     }
                     if (DEBUG) {
                         long totalTime = SystemClock.elapsedRealtimeNanos() - startTime;
@@ -751,10 +788,12 @@
                                 TimeUnit.MILLISECONDS);
                         if (!success) {
                             Log.e(TAG, "Timed out while queueing cleanup request.");
+                            mCaptureCollector.failAll();
                         }
                     } catch (InterruptedException e) {
-                        // TODO: report error to CameraDevice
-                        Log.e(TAG, "Interrupted while waiting for requests to complete.");
+                        Log.e(TAG, "Interrupted while waiting for requests to complete: ", e);
+                        mDeviceState.setError(
+                                CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
                     }
                     if (mGLThreadManager != null) {
                         mGLThreadManager.quit();
@@ -802,11 +841,15 @@
     }
 
     /**
-     * Flush the pending requests.
+     * Flush any pending requests.
+     *
+     * @return the last frame number.
      */
-    public void flush() {
-        // TODO: Implement flush.
-        Log.e(TAG, "flush not yet implemented.");
+    public long flush() {
+        Log.i(TAG, "Flushing all pending requests.");
+        long lastFrame = mRequestQueue.stopRepeating();
+        mCaptureCollector.failAll();
+        return lastFrame;
     }
 
     /**
@@ -856,7 +899,6 @@
         return mRequestQueue.stopRepeating(requestId);
     }
 
-
     /**
      * Configure with the current list of output Surfaces.
      *
diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
index a35883c..c018c3e 100644
--- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
+++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
@@ -652,7 +652,9 @@
 
         // No preview request queued, drop frame.
         if (captureHolder == null) {
-            Log.w(TAG, "Dropping preview frame.");
+            if (DEBUG) {
+                Log.d(TAG, "Dropping preview frame.");
+            }
             if (doTiming) {
                 endGlTiming();
             }
diff --git a/core/java/android/hardware/camera2/utils/ArrayUtils.java b/core/java/android/hardware/camera2/utils/ArrayUtils.java
index ae97079..5a78bbd 100644
--- a/core/java/android/hardware/camera2/utils/ArrayUtils.java
+++ b/core/java/android/hardware/camera2/utils/ArrayUtils.java
@@ -32,7 +32,7 @@
 
     /** Return the index of {@code needle} in the {@code array}, or else {@code -1} */
     public static <T> int getArrayIndex(T[] array, T needle) {
-        if (needle == null) {
+        if (array == null) {
             return -1;
         }
 
@@ -167,6 +167,17 @@
         return getArrayIndex(array, elem) != -1;
     }
 
+    /**
+     * Returns true if the given {@code array} contains the given element.
+     *
+     * @param array {@code array} to check for {@code elem}
+     * @param elem {@code elem} to test for
+     * @return {@code true} if the given element is contained
+     */
+    public static <T> boolean contains(T[] array, T elem) {
+        return getArrayIndex(array, elem) != -1;
+    }
+
     private ArrayUtils() {
         throw new AssertionError();
     }