Merge "ImageWriter: Exception when Surface is abandoned" into mnc-dev
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index c547b06..62ebfb3 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -497,8 +497,9 @@
      *
      * <p>Each reprocessable capture session has an input {@link Surface} where the reprocess
      * capture requests get the input images from, rather than the camera device. The application
-     * can create a {@link android.media.ImageWriter} with this input {@link Surface} and use it to
-     * provide input images for reprocess capture requests.</p>
+     * can create a {@link android.media.ImageWriter ImageWriter} with this input {@link Surface}
+     * and use it to provide input images for reprocess capture requests. When the reprocessable
+     * capture session is closed, the input {@link Surface} is abandoned and becomes invalid.</p>
      *
      * @return The {@link Surface} where reprocessing capture requests get the input images from. If
      *         this is not a reprocess capture session, {@code null} will be returned.
diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java
index 2ef2519..9fb3286 100644
--- a/media/java/android/media/ImageWriter.java
+++ b/media/java/android/media/ImageWriter.java
@@ -40,8 +40,8 @@
  * Several Android API classes can provide input {@link android.view.Surface
  * Surface} objects for ImageWriter to produce data into, including
  * {@link MediaCodec MediaCodec} (encoder),
- * {@link android.hardware.camera2.CameraDevice CameraDevice} (reprocessing
- * input), {@link ImageReader}, etc.
+ * {@link android.hardware.camera2.CameraCaptureSession CameraCaptureSession}
+ * (reprocessing input), {@link ImageReader}, etc.
  * </p>
  * <p>
  * The input Image data is encapsulated in {@link Image} objects. To produce
@@ -64,7 +64,14 @@
  * {@link android.hardware.camera2.CameraDevice}) to consume the Images. If the
  * downstream components cannot consume the Images at least as fast as the
  * ImageWriter production rate, the {@link #dequeueInputImage} call will
- * eventually block and the application will have to drop input frames. </p>
+ * eventually block and the application will have to drop input frames.
+ * </p>
+ * <p>
+ * If the consumer component that provided the input {@link android.view.Surface Surface}
+ * abandons the {@link android.view.Surface Surface}, {@link #queueInputImage queueing}
+ * or {@link #dequeueInputImage dequeueing} an {@link Image} will throw an
+ * {@link IllegalStateException}.
+ * </p>
  */
 public class ImageWriter implements AutoCloseable {
     private final Object mListenerLock = new Object();
@@ -188,7 +195,9 @@
      * @return The next available input Image from this ImageWriter.
      * @throws IllegalStateException if {@code maxImages} Images are currently
      *             dequeued, or the ImageWriter format is
-     *             {@link ImageFormat#PRIVATE PRIVATE}.
+     *             {@link ImageFormat#PRIVATE PRIVATE}, or the input
+     *             {@link android.view.Surface Surface} has been abandoned by the
+     *             consumer component that provided the {@link android.view.Surface Surface}.
      * @see #queueInputImage
      * @see Image#close
      */
@@ -254,6 +263,11 @@
      *
      * @param image The Image to be queued back to ImageWriter for future
      *            consumption.
+     * @throws IllegalStateException if the image was already queued previously,
+     *            or the image was aborted previously, or the input
+     *            {@link android.view.Surface Surface} has been abandoned by the
+     *            consumer component that provided the
+     *            {@link android.view.Surface Surface}.
      * @see #dequeueInputImage()
      */
     public void queueInputImage(Image image) {
@@ -699,7 +713,7 @@
         }
 
         private void clearSurfacePlanes() {
-            if (mIsImageValid) {
+            if (mIsImageValid && mPlanes != null) {
                 for (int i = 0; i < mPlanes.length; i++) {
                     if (mPlanes[i] != null) {
                         mPlanes[i].clearBuffer();
diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp
index ba7634c..f92a8ef 100644
--- a/media/jni/android_media_ImageWriter.cpp
+++ b/media/jni/android_media_ImageWriter.cpp
@@ -338,9 +338,16 @@
     int fenceFd = -1;
     status_t res = anw->dequeueBuffer(anw.get(), &anb, &fenceFd);
     if (res != OK) {
-        // TODO: handle different error cases here.
         ALOGE("%s: Dequeue buffer failed: %s (%d)", __FUNCTION__, strerror(-res), res);
-        jniThrowRuntimeException(env, "dequeue buffer failed");
+        switch (res) {
+            case NO_INIT:
+                jniThrowException(env, "java/lang/IllegalStateException",
+                    "Surface has been abandoned");
+                break;
+            default:
+                // TODO: handle other error cases here.
+                jniThrowRuntimeException(env, "dequeue buffer failed");
+        }
         return;
     }
     // New GraphicBuffer object doesn't own the handle, thus the native buffer
@@ -468,7 +475,16 @@
     // Finally, queue input buffer
     res = anw->queueBuffer(anw.get(), buffer, fenceFd);
     if (res != OK) {
-        jniThrowRuntimeException(env, "Queue input buffer failed");
+        ALOGE("%s: Queue buffer failed: %s (%d)", __FUNCTION__, strerror(-res), res);
+        switch (res) {
+            case NO_INIT:
+                jniThrowException(env, "java/lang/IllegalStateException",
+                    "Surface has been abandoned");
+                break;
+            default:
+                // TODO: handle other error cases here.
+                jniThrowRuntimeException(env, "Queue input buffer failed");
+        }
         return;
     }
 
@@ -514,9 +530,16 @@
     // Step 1. Attach Image
     res = surface->attachBuffer(opaqueBuffer->mGraphicBuffer.get());
     if (res != OK) {
-        // TODO: handle different error case separately.
         ALOGE("Attach image failed: %s (%d)", strerror(-res), res);
-        jniThrowRuntimeException(env, "nativeAttachImage failed!!!");
+        switch (res) {
+            case NO_INIT:
+                jniThrowException(env, "java/lang/IllegalStateException",
+                    "Surface has been abandoned");
+                break;
+            default:
+                // TODO: handle other error cases here.
+                jniThrowRuntimeException(env, "nativeAttachImage failed!!!");
+        }
         return res;
     }
     sp < ANativeWindow > anw = surface;
@@ -545,7 +568,16 @@
     res = anw->queueBuffer(anw.get(), opaqueBuffer->mGraphicBuffer.get(), /*fenceFd*/
             -1);
     if (res != OK) {
-        jniThrowRuntimeException(env, "Queue input buffer failed");
+        ALOGE("%s: Queue buffer failed: %s (%d)", __FUNCTION__, strerror(-res), res);
+        switch (res) {
+            case NO_INIT:
+                jniThrowException(env, "java/lang/IllegalStateException",
+                    "Surface has been abandoned");
+                break;
+            default:
+                // TODO: handle other error cases here.
+                jniThrowRuntimeException(env, "Queue input buffer failed");
+        }
         return res;
     }