Merge "camera2: Hide JPEGs in RGBA gralloc buffers." into lmp-dev
diff --git a/core/java/android/hardware/camera2/legacy/BurstHolder.java b/core/java/android/hardware/camera2/legacy/BurstHolder.java
index c141c51..b9c89f8 100644
--- a/core/java/android/hardware/camera2/legacy/BurstHolder.java
+++ b/core/java/android/hardware/camera2/legacy/BurstHolder.java
@@ -22,6 +22,7 @@
 import android.view.Surface;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -38,14 +39,16 @@
      *
      * @param requestId id of the burst request.
      * @param repeating true if this burst is repeating.
-     * @param requests a {@link java.util.List} of {@link CaptureRequest}s in this burst.
+     * @param requests a {@link List} of {@link CaptureRequest}s in this burst.
+     * @param jpegSurfaceIds a {@link Collection} of IDs for the surfaces that have jpeg outputs.
      */
-    public BurstHolder(int requestId, boolean repeating, List<CaptureRequest> requests) {
-        mRequestBuilders = new ArrayList<RequestHolder.Builder>();
+    public BurstHolder(int requestId, boolean repeating, List<CaptureRequest> requests,
+                       Collection<Long> jpegSurfaceIds) {
+        mRequestBuilders = new ArrayList<>();
         int i = 0;
         for (CaptureRequest r : requests) {
             mRequestBuilders.add(new RequestHolder.Builder(requestId, /*subsequenceId*/i,
-                    /*request*/r, repeating));
+                    /*request*/r, repeating, jpegSurfaceIds));
             ++i;
         }
         mRepeating = repeating;
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index a724b41..4587c6f 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -522,7 +522,7 @@
         return surfaceIds;
     }
 
-    static boolean containsSurfaceId(Surface s, List<Long> ids) {
+    static boolean containsSurfaceId(Surface s, Collection<Long> ids) {
         long id = getSurfaceId(s);
         return ids.contains(id);
     }
diff --git a/core/java/android/hardware/camera2/legacy/RequestHolder.java b/core/java/android/hardware/camera2/legacy/RequestHolder.java
index 69c140b..edd8e4e 100644
--- a/core/java/android/hardware/camera2/legacy/RequestHolder.java
+++ b/core/java/android/hardware/camera2/legacy/RequestHolder.java
@@ -43,71 +43,6 @@
     private volatile boolean mFailed = false;
 
     /**
-     * Returns true if the given surface requires jpeg buffers.
-     *
-     * @param s a {@link android.view.Surface} to check.
-     * @return true if the surface requires a jpeg buffer.
-     */
-    public static boolean jpegType(Surface s)
-            throws LegacyExceptionUtils.BufferQueueAbandonedException {
-        return LegacyCameraDevice.detectSurfaceType(s) ==
-                CameraMetadataNative.NATIVE_JPEG_FORMAT;
-    }
-
-    /**
-     * Returns true if the given surface requires non-jpeg buffer types.
-     *
-     * <p>
-     * "Jpeg buffer" refers to the buffers returned in the jpeg
-     * {@link android.hardware.Camera.PictureCallback}.  Non-jpeg buffers are created using a tee
-     * of the preview stream drawn to the surface
-     * set via {@link android.hardware.Camera#setPreviewDisplay(android.view.SurfaceHolder)} or
-     * equivalent methods.
-     * </p>
-     * @param s a {@link android.view.Surface} to check.
-     * @return true if the surface requires a non-jpeg buffer type.
-     */
-    public static boolean previewType(Surface s)
-            throws LegacyExceptionUtils.BufferQueueAbandonedException {
-        return LegacyCameraDevice.detectSurfaceType(s) !=
-                CameraMetadataNative.NATIVE_JPEG_FORMAT;
-    }
-
-    /**
-     * Returns the number of surfaces targeted by the request that require jpeg buffers.
-     */
-    private static int numJpegTargets(CaptureRequest request) {
-        int count = 0;
-        for (Surface s : request.getTargets()) {
-            try {
-                if (jpegType(s)) {
-                    ++count;
-                }
-            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
-                Log.w(TAG, "Surface abandoned, skipping...", e);
-            }
-        }
-        return count;
-    }
-
-    /**
-     * Returns the number of surfaces targeted by the request that require non-jpeg buffers.
-     */
-    private static int numPreviewTargets(CaptureRequest request) {
-        int count = 0;
-        for (Surface s : request.getTargets()) {
-            try {
-                if (previewType(s)) {
-                    ++count;
-                }
-            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
-                Log.w(TAG, "Surface abandoned, skipping...", e);
-            }
-        }
-        return count;
-    }
-
-    /**
      * A builder class for {@link RequestHolder} objects.
      *
      * <p>
@@ -121,6 +56,7 @@
         private final boolean mRepeating;
         private final int mNumJpegTargets;
         private final int mNumPreviewTargets;
+        private final Collection<Long> mJpegSurfaceIds;
 
         /**
          * Construct a new {@link Builder} to generate {@link RequestHolder} objects.
@@ -132,17 +68,81 @@
          * @param repeating {@code true} if the request is repeating.
          */
         public Builder(int requestId, int subsequenceId, CaptureRequest request,
-                       boolean repeating) {
+                       boolean repeating, Collection<Long> jpegSurfaceIds) {
             checkNotNull(request, "request must not be null");
             mRequestId = requestId;
             mSubsequenceId = subsequenceId;
             mRequest = request;
             mRepeating = repeating;
+            mJpegSurfaceIds = jpegSurfaceIds;
             mNumJpegTargets = numJpegTargets(mRequest);
             mNumPreviewTargets = numPreviewTargets(mRequest);
         }
 
         /**
+         * Returns true if the given surface requires jpeg buffers.
+         *
+         * @param s a {@link android.view.Surface} to check.
+         * @return true if the surface requires a jpeg buffer.
+         */
+        private boolean jpegType(Surface s)
+                throws LegacyExceptionUtils.BufferQueueAbandonedException {
+            return LegacyCameraDevice.containsSurfaceId(s, mJpegSurfaceIds);
+        }
+
+        /**
+         * Returns true if the given surface requires non-jpeg buffer types.
+         *
+         * <p>
+         * "Jpeg buffer" refers to the buffers returned in the jpeg
+         * {@link android.hardware.Camera.PictureCallback}.  Non-jpeg buffers are created using a tee
+         * of the preview stream drawn to the surface
+         * set via {@link android.hardware.Camera#setPreviewDisplay(android.view.SurfaceHolder)} or
+         * equivalent methods.
+         * </p>
+         * @param s a {@link android.view.Surface} to check.
+         * @return true if the surface requires a non-jpeg buffer type.
+         */
+        private boolean previewType(Surface s)
+                throws LegacyExceptionUtils.BufferQueueAbandonedException {
+            return !jpegType(s);
+        }
+
+        /**
+         * Returns the number of surfaces targeted by the request that require jpeg buffers.
+         */
+        private int numJpegTargets(CaptureRequest request) {
+            int count = 0;
+            for (Surface s : request.getTargets()) {
+                try {
+                    if (jpegType(s)) {
+                        ++count;
+                    }
+                } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+                    Log.d(TAG, "Surface abandoned, skipping...", e);
+                }
+            }
+            return count;
+        }
+
+        /**
+         * Returns the number of surfaces targeted by the request that require non-jpeg buffers.
+         */
+        private int numPreviewTargets(CaptureRequest request) {
+            int count = 0;
+            for (Surface s : request.getTargets()) {
+                try {
+                    if (previewType(s)) {
+                        ++count;
+                    }
+                } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+                    Log.d(TAG, "Surface abandoned, skipping...", e);
+                }
+            }
+            return count;
+        }
+
+        /**
          * Build a new {@link RequestHolder} using with parameters generated from this
          *      {@link Builder}.
          *
diff --git a/core/java/android/hardware/camera2/legacy/RequestQueue.java b/core/java/android/hardware/camera2/legacy/RequestQueue.java
index 7598f99..c995029 100644
--- a/core/java/android/hardware/camera2/legacy/RequestQueue.java
+++ b/core/java/android/hardware/camera2/legacy/RequestQueue.java
@@ -39,8 +39,11 @@
     private long mCurrentFrameNumber = 0;
     private long mCurrentRepeatingFrameNumber = INVALID_FRAME;
     private int mCurrentRequestId = 0;
+    private final List<Long> mJpegSurfaceIds;
 
-    public RequestQueue() {}
+    public RequestQueue(List<Long> jpegSurfaceIds) {
+        mJpegSurfaceIds = jpegSurfaceIds;
+    }
 
     /**
      * Return and remove the next burst on the queue.
@@ -117,7 +120,7 @@
     public synchronized int submit(List<CaptureRequest> requests, boolean repeating,
             /*out*/LongParcelable frameNumber) {
         int requestId = mCurrentRequestId++;
-        BurstHolder burst = new BurstHolder(requestId, repeating, requests);
+        BurstHolder burst = new BurstHolder(requestId, repeating, requests, mJpegSurfaceIds);
         long ret = INVALID_FRAME;
         if (burst.isRepeating()) {
             Log.i(TAG, "Repeating capture request set.");
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index 4f8fe0b..a9981d8 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -91,9 +91,11 @@
     private SurfaceTexture mPreviewTexture;
     private Camera.Parameters mParams;
 
+    private final List<Long> mJpegSurfaceIds = new ArrayList<>();
+
     private Size mIntermediateBufferSize;
 
-    private final RequestQueue mRequestQueue = new RequestQueue();
+    private final RequestQueue mRequestQueue = new RequestQueue(mJpegSurfaceIds);
     private LegacyRequest mLastRequest = null;
     private SurfaceTexture mDummyTexture;
     private Surface mDummySurface;
@@ -102,6 +104,10 @@
     private final FpsCounter mPrevCounter = new FpsCounter("Incoming Preview");
     private final FpsCounter mRequestCounter = new FpsCounter("Incoming Requests");
 
+    // Stuff JPEGs into HAL_PIXEL_FORMAT_RGBA_8888 gralloc buffers to get around SW write
+    // limitations for (b/17379185).
+    private static final boolean USE_BLOB_FORMAT_OVERRIDE = true;
+
     /**
      * Container object for Configure messages.
      */
@@ -197,10 +203,13 @@
             }
             for (Surface s : holder.getHolderTargets()) {
                 try {
-                    if (RequestHolder.jpegType(s)) {
+                    if (LegacyCameraDevice.containsSurfaceId(s, mJpegSurfaceIds)) {
                         Log.i(TAG, "Producing jpeg buffer...");
-                        LegacyCameraDevice.setSurfaceDimens(s, data.length +
-                                LegacyCameraDevice.nativeGetJpegFooterSize(), /*height*/1);
+
+                        int totalSize = data.length + LegacyCameraDevice.nativeGetJpegFooterSize();
+                        totalSize += ((totalSize - 1) & ~0x3) + 4; // align to next octonibble
+
+                        LegacyCameraDevice.setSurfaceDimens(s, totalSize, /*height*/1);
                         LegacyCameraDevice.setNextTimestamp(s, timestamp);
                         LegacyCameraDevice.produceFrame(s, data, data.length, /*height*/1,
                                 CameraMetadataNative.NATIVE_JPEG_FORMAT);
@@ -316,6 +325,7 @@
             mGLThreadManager.ignoreNewFrames();
             mGLThreadManager.waitUntilIdle();
         }
+        resetJpegSurfaceFormats(mCallbackOutputs);
         mPreviewOutputs.clear();
         mCallbackOutputs.clear();
         mPreviewTexture = null;
@@ -329,6 +339,12 @@
                     LegacyCameraDevice.setSurfaceOrientation(s, facing, orientation);
                     switch (format) {
                         case CameraMetadataNative.NATIVE_JPEG_FORMAT:
+                            if (USE_BLOB_FORMAT_OVERRIDE) {
+                                // Override to RGBA_8888 format.
+                                LegacyCameraDevice.setSurfaceFormat(s,
+                                        LegacyMetadataMapper.HAL_PIXEL_FORMAT_RGBA_8888);
+                            }
+                            mJpegSurfaceIds.add(LegacyCameraDevice.getSurfaceId(s));
                             mCallbackOutputs.add(s);
                             break;
                         default:
@@ -426,8 +442,19 @@
         }
 
         mCamera.setParameters(mParams);
-        // TODO: configure the JPEG surface with some arbitrary size
-        // using LegacyCameraDevice.nativeConfigureSurface
+    }
+
+    private void resetJpegSurfaceFormats(Collection<Surface> surfaces) {
+        if (!USE_BLOB_FORMAT_OVERRIDE || surfaces == null) {
+            return;
+        }
+        for(Surface s : surfaces) {
+            try {
+                LegacyCameraDevice.setSurfaceFormat(s, LegacyMetadataMapper.HAL_PIXEL_FORMAT_BLOB);
+            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+                Log.w(TAG, "Surface abandoned, skipping...", e);
+            }
+        }
     }
 
     /**
@@ -459,9 +486,8 @@
         List<Size> configuredJpegSizes = new ArrayList<Size>();
         for (Surface callbackSurface : callbackOutputs) {
             try {
-                int format = LegacyCameraDevice.detectSurfaceType(callbackSurface);
 
-                if (format != CameraMetadataNative.NATIVE_JPEG_FORMAT) {
+                if (!LegacyCameraDevice.containsSurfaceId(callbackSurface, mJpegSurfaceIds)) {
                     continue; // Ignore non-JPEG callback formats
                 }
 
@@ -821,6 +847,7 @@
                     if (mCamera != null) {
                         mCamera.release();
                     }
+                    resetJpegSurfaceFormats(mCallbackOutputs);
                     break;
                 default:
                     throw new AssertionError("Unhandled message " + msg.what +
diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
index c018c3e..c0d1d5e 100644
--- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
+++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
@@ -581,6 +581,7 @@
             // If pixel conversions aren't handled by egl, use a pbuffer
             try {
                 if (LegacyCameraDevice.needsConversion(s)) {
+                    // Always override to YV12 output for YUV surface formats.
                     LegacyCameraDevice.setSurfaceFormat(s, ImageFormat.YV12);
                     EGLSurfaceHolder holder = new EGLSurfaceHolder();
                     holder.surface = s;
diff --git a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
index 980ead0..ee00161 100644
--- a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
+++ b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
@@ -200,7 +200,7 @@
     switch(pixelFmt) {
         case HAL_PIXEL_FORMAT_YCrCb_420_SP: {
             if (bufSize < width * height * 4) {
-                ALOGE("%s: PixelBuffer size %" PRId32 " to small for given dimensions",
+                ALOGE("%s: PixelBuffer size %" PRId32 " too small for given dimensions",
                         __FUNCTION__, bufSize);
                 return BAD_VALUE;
             }
@@ -222,7 +222,7 @@
         }
         case HAL_PIXEL_FORMAT_YV12: {
             if (bufSize < width * height * 4) {
-                ALOGE("%s: PixelBuffer size %" PRId32 " to small for given dimensions",
+                ALOGE("%s: PixelBuffer size %" PRId32 " too small for given dimensions",
                         __FUNCTION__, bufSize);
                 return BAD_VALUE;
             }
@@ -259,7 +259,7 @@
             // Software writes with YCbCr_420_888 format are unsupported
             // by the gralloc module for now
             if (bufSize < width * height * 4) {
-                ALOGE("%s: PixelBuffer size %" PRId32 " to small for given dimensions",
+                ALOGE("%s: PixelBuffer size %" PRId32 " too small for given dimensions",
                         __FUNCTION__, bufSize);
                 return BAD_VALUE;
             }
@@ -281,6 +281,18 @@
                 return BAD_VALUE;
             }
             int8_t* img = NULL;
+            struct camera3_jpeg_blob footer = {
+                jpeg_blob_id: CAMERA3_JPEG_BLOB_ID,
+                jpeg_size: (uint32_t)width
+            };
+
+            size_t totalSize = static_cast<size_t>(width) + sizeof(footer);
+            size_t padding = ((totalSize - 1) & ~0x3) + 4; // align to next octonibble
+            totalSize += padding;
+            if (anb->width != totalSize) {
+                ALOGE("%s: gralloc buffer wrong size to hold jpeg, failed to produce buffer.");
+                return BAD_VALUE;
+            }
 
             ALOGV("%s: Lock buffer from %p for write", __FUNCTION__, anw.get());
             err = buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
@@ -289,12 +301,9 @@
                         err);
                 return err;
             }
-            struct camera3_jpeg_blob footer = {
-                jpeg_blob_id: CAMERA3_JPEG_BLOB_ID,
-                jpeg_size: (uint32_t)width
-            };
             memcpy(img, pixelBuffer, width);
-            memcpy(img + anb->width - sizeof(footer), &footer, sizeof(footer));
+            memset(img + width, 0, padding);
+            memcpy(img + totalSize - sizeof(footer), &footer, sizeof(footer));
             break;
         }
         default: {
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index b786f94..b541454 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -642,7 +642,7 @@
         private void createSurfacePlanes() {
             mPlanes = new SurfacePlane[ImageReader.this.mNumPlanes];
             for (int i = 0; i < ImageReader.this.mNumPlanes; i++) {
-                mPlanes[i] = nativeCreatePlane(i);
+                mPlanes[i] = nativeCreatePlane(i, ImageReader.this.mFormat);
             }
         }
         private class SurfacePlane extends android.media.Image.Plane {
@@ -661,7 +661,8 @@
                 if (mBuffer != null) {
                     return mBuffer;
                 } else {
-                    mBuffer = SurfaceImage.this.nativeImageGetBuffer(mIndex);
+                    mBuffer = SurfaceImage.this.nativeImageGetBuffer(mIndex,
+                            ImageReader.this.mFormat);
                     // Set the byteBuffer order according to host endianness (native order),
                     // otherwise, the byteBuffer order defaults to ByteOrder.BIG_ENDIAN.
                     return mBuffer.order(ByteOrder.nativeOrder());
@@ -711,8 +712,8 @@
         private SurfacePlane[] mPlanes;
         private boolean mIsImageValid;
 
-        private synchronized native ByteBuffer nativeImageGetBuffer(int idx);
-        private synchronized native SurfacePlane nativeCreatePlane(int idx);
+        private synchronized native ByteBuffer nativeImageGetBuffer(int idx, int readerFormat);
+        private synchronized native SurfacePlane nativeCreatePlane(int idx, int readerFormat);
     }
 
     private synchronized native void nativeInit(Object weakSelf, int w, int h,
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index fa4439d..a734774 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -317,8 +317,18 @@
     return size;
 }
 
+static int32_t applyFormatOverrides(int32_t bufferFormat, int32_t readerCtxFormat)
+{
+    // Using HAL_PIXEL_FORMAT_RGBA_8888 gralloc buffers containing JPEGs to get around SW
+    // write limitations for some platforms (b/17379185).
+    if (readerCtxFormat == HAL_PIXEL_FORMAT_BLOB && bufferFormat == HAL_PIXEL_FORMAT_RGBA_8888) {
+        return HAL_PIXEL_FORMAT_BLOB;
+    }
+    return bufferFormat;
+}
+
 static void Image_getLockedBufferInfo(JNIEnv* env, CpuConsumer::LockedBuffer* buffer, int idx,
-                                uint8_t **base, uint32_t *size)
+                                uint8_t **base, uint32_t *size, int32_t readerFormat)
 {
     ALOG_ASSERT(buffer != NULL, "Input buffer is NULL!!!");
     ALOG_ASSERT(base != NULL, "base is NULL!!!");
@@ -334,6 +344,8 @@
 
     dataSize = ySize = cSize = cStride = 0;
     int32_t fmt = buffer->format;
+
+    fmt = applyFormatOverrides(fmt, readerFormat);
     switch (fmt) {
         case HAL_PIXEL_FORMAT_YCbCr_420_888:
             pData =
@@ -458,7 +470,8 @@
     *size = dataSize;
 }
 
-static jint Image_imageGetPixelStride(JNIEnv* env, CpuConsumer::LockedBuffer* buffer, int idx)
+static jint Image_imageGetPixelStride(JNIEnv* env, CpuConsumer::LockedBuffer* buffer, int idx,
+        int32_t readerFormat)
 {
     ALOGV("%s: buffer index: %d", __FUNCTION__, idx);
     ALOG_ASSERT((idx < IMAGE_READER_MAX_NUM_PLANES) && (idx >= 0), "Index is out of range:%d", idx);
@@ -467,6 +480,9 @@
     ALOG_ASSERT(buffer != NULL, "buffer is NULL");
 
     int32_t fmt = buffer->format;
+
+    fmt = applyFormatOverrides(fmt, readerFormat);
+
     switch (fmt) {
         case HAL_PIXEL_FORMAT_YCbCr_420_888:
             pixelStride = (idx == 0) ? 1 : buffer->chromaStep;
@@ -515,7 +531,8 @@
     return pixelStride;
 }
 
-static jint Image_imageGetRowStride(JNIEnv* env, CpuConsumer::LockedBuffer* buffer, int idx)
+static jint Image_imageGetRowStride(JNIEnv* env, CpuConsumer::LockedBuffer* buffer, int idx,
+        int32_t readerFormat)
 {
     ALOGV("%s: buffer index: %d", __FUNCTION__, idx);
     ALOG_ASSERT((idx < IMAGE_READER_MAX_NUM_PLANES) && (idx >= 0));
@@ -525,6 +542,8 @@
 
     int32_t fmt = buffer->format;
 
+    fmt = applyFormatOverrides(fmt, readerFormat);
+
     switch (fmt) {
         case HAL_PIXEL_FORMAT_YCbCr_420_888:
             rowStride = (idx == 0) ? buffer->stride : buffer->chromaStride;
@@ -777,9 +796,10 @@
         outputHeight = buffer->crop.getHeight();
     }
 
+    int imgReaderFmt = ctx->getBufferFormat();
     int imageReaderWidth = ctx->getBufferWidth();
     int imageReaderHeight = ctx->getBufferHeight();
-    if ((buffer->format != HAL_PIXEL_FORMAT_BLOB) &&
+    if ((buffer->format != HAL_PIXEL_FORMAT_BLOB) && (imgReaderFmt != HAL_PIXEL_FORMAT_BLOB) &&
             (imageReaderWidth != outputWidth || imageReaderHeight > outputHeight)) {
         /**
          * For video decoder, the buffer height is actually the vertical stride,
@@ -795,15 +815,19 @@
         return -1;
     }
 
-    int imgReaderFmt = ctx->getBufferFormat();
     int bufFmt = buffer->format;
     if (imgReaderFmt != bufFmt) {
-        // Special casing for when producer switches to a format compatible with flexible YUV
-        // (HAL_PIXEL_FORMAT_YCbCr_420_888).
+
         if (imgReaderFmt == HAL_PIXEL_FORMAT_YCbCr_420_888 && (bufFmt ==
                 HAL_PIXEL_FORMAT_YCrCb_420_SP || bufFmt == HAL_PIXEL_FORMAT_YV12)) {
+            // Special casing for when producer switches to a format compatible with flexible YUV
+            // (HAL_PIXEL_FORMAT_YCbCr_420_888).
             ctx->setBufferFormat(bufFmt);
-            ALOGV("%s: Overriding buffer format YUV_420_888 to %x.", __FUNCTION__, bufFmt);
+            ALOGD("%s: Overriding buffer format YUV_420_888 to %x.", __FUNCTION__, bufFmt);
+        } else if (imgReaderFmt == HAL_PIXEL_FORMAT_BLOB && bufFmt == HAL_PIXEL_FORMAT_RGBA_8888) {
+            // Using HAL_PIXEL_FORMAT_RGBA_8888 gralloc buffers containing JPEGs to get around SW
+            // write limitations for (b/17379185).
+            ALOGD("%s: Receiving JPEG in HAL_PIXEL_FORMAT_RGBA_8888 buffer.", __FUNCTION__);
         } else {
             // Return the buffer to the queue.
             consumer->unlockBuffer(*buffer);
@@ -843,7 +867,7 @@
     return android_view_Surface_createFromIGraphicBufferProducer(env, gbp);
 }
 
-static jobject Image_createSurfacePlane(JNIEnv* env, jobject thiz, int idx)
+static jobject Image_createSurfacePlane(JNIEnv* env, jobject thiz, int idx, int readerFormat)
 {
     int rowStride, pixelStride;
     ALOGV("%s: buffer index: %d", __FUNCTION__, idx);
@@ -854,8 +878,11 @@
     if (buffer == NULL) {
         jniThrowException(env, "java/lang/IllegalStateException", "Image was released");
     }
-    rowStride = Image_imageGetRowStride(env, buffer, idx);
-    pixelStride = Image_imageGetPixelStride(env, buffer, idx);
+
+    readerFormat = Image_getPixelFormat(env, readerFormat);
+
+    rowStride = Image_imageGetRowStride(env, buffer, idx, readerFormat);
+    pixelStride = Image_imageGetPixelStride(env, buffer, idx, readerFormat);
 
     jobject surfPlaneObj = env->NewObject(gSurfacePlaneClassInfo.clazz,
             gSurfacePlaneClassInfo.ctor, thiz, idx, rowStride, pixelStride);
@@ -863,7 +890,7 @@
     return surfPlaneObj;
 }
 
-static jobject Image_getByteBuffer(JNIEnv* env, jobject thiz, int idx)
+static jobject Image_getByteBuffer(JNIEnv* env, jobject thiz, int idx, int readerFormat)
 {
     uint8_t *base = NULL;
     uint32_t size = 0;
@@ -877,8 +904,10 @@
         jniThrowException(env, "java/lang/IllegalStateException", "Image was released");
     }
 
+    readerFormat = Image_getPixelFormat(env, readerFormat);
+
     // Create byteBuffer from native buffer
-    Image_getLockedBufferInfo(env, buffer, idx, &base, &size);
+    Image_getLockedBufferInfo(env, buffer, idx, &base, &size, readerFormat);
 
     if (size > static_cast<uint32_t>(INT32_MAX)) {
         // Byte buffer have 'int capacity', so check the range
@@ -910,8 +939,8 @@
 };
 
 static JNINativeMethod gImageMethods[] = {
-    {"nativeImageGetBuffer",   "(I)Ljava/nio/ByteBuffer;",   (void*)Image_getByteBuffer },
-    {"nativeCreatePlane",      "(I)Landroid/media/ImageReader$SurfaceImage$SurfacePlane;",
+    {"nativeImageGetBuffer",   "(II)Ljava/nio/ByteBuffer;",   (void*)Image_getByteBuffer },
+    {"nativeCreatePlane",      "(II)Landroid/media/ImageReader$SurfaceImage$SurfacePlane;",
                                                              (void*)Image_createSurfacePlane },
 };