media: Update ImageReader to remove MaxImagesAcquiredException

* acquiring images now throws IllegalStateException instead of
    MaxImagesAcquiredException

Bug: 10691447
Change-Id: I7ce68f990fb96703705b9181012a28633fea0b7a
diff --git a/api/current.txt b/api/current.txt
index b0a18de..424a376 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -12348,8 +12348,8 @@
   }
 
   public class ImageReader implements java.lang.AutoCloseable {
-    method public android.media.Image acquireLatestImage() throws android.media.ImageReader.MaxImagesAcquiredException;
-    method public android.media.Image acquireNextImage() throws android.media.ImageReader.MaxImagesAcquiredException;
+    method public android.media.Image acquireLatestImage();
+    method public android.media.Image acquireNextImage();
     method public void close();
     method public int getHeight();
     method public int getImageFormat();
@@ -12360,13 +12360,6 @@
     method public void setOnImageAvailableListener(android.media.ImageReader.OnImageAvailableListener, android.os.Handler);
   }
 
-  public static class ImageReader.MaxImagesAcquiredException extends java.lang.Exception {
-    ctor public ImageReader.MaxImagesAcquiredException();
-    ctor public ImageReader.MaxImagesAcquiredException(java.lang.String);
-    ctor public ImageReader.MaxImagesAcquiredException(java.lang.String, java.lang.Throwable);
-    ctor public ImageReader.MaxImagesAcquiredException(java.lang.Throwable);
-  }
-
   public static abstract interface ImageReader.OnImageAvailableListener {
     method public abstract void onImageAvailable(android.media.ImageReader);
   }
diff --git a/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java b/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
index 8278579..a2e9ae8 100644
--- a/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
+++ b/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
@@ -26,7 +26,6 @@
 import android.hardware.display.VirtualDisplay;
 import android.media.Image;
 import android.media.ImageReader;
-import android.media.ImageReader.MaxImagesAcquiredException;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -427,9 +426,6 @@
                         image.close();
                     }
                 }
-            } catch (MaxImagesAcquiredException e) {
-                // We should never try to consume more buffers than maxImages.
-                throw new IllegalStateException(e);
             } finally {
                 mImageReaderLock.unlock();
             }
diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java
index 915b09e..a346e17 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -40,8 +40,7 @@
  * availability of new Images once
  * {@link ImageReader#getMaxImages the maximum outstanding image count} is
  * reached. When this happens, the function acquiring new Images will typically
- * throw a
- * {@link ImageReader.MaxImagesAcquiredException MaxImagesAcquiredException}.</p>
+ * throw an {@link IllegalStateException}.</p>
  *
  * @see ImageReader
  */
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index e5bcb3e..aee8362 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -49,43 +49,20 @@
 public class ImageReader implements AutoCloseable {
 
     /**
-     * <p>
-     * This exception is thrown when the user of an {@link ImageReader} tries to acquire a new
-     * {@link Image} when the maximum number of {@link Image Images} have already been acquired.
-     * The maximum number is determined by the {@code maxBuffers} argument of
-     * {@link ImageReader#newInstance newInstance}.
-     * </p>
-     *
-     * <p>
-     * To recover from this exception, release existing {@link Image images} back to the
-     * reader with {@link Image#close}.
-     * </p>
-     *
-     * @see Image#close
-     * @see ImageReader#acquireLatestImage
-     * @see ImageReader#acquireNextImage
+     * Returned by nativeImageSetup when acquiring the image was successful.
      */
-    public static class MaxImagesAcquiredException extends Exception {
-        /**
-         * Suppress Eclipse warnings
-         */
-        private static final long serialVersionUID = 761231231236L;
-
-        public MaxImagesAcquiredException() {
-        }
-
-        public MaxImagesAcquiredException(String message) {
-            super(message);
-        }
-
-        public MaxImagesAcquiredException(String message, Throwable throwable) {
-            super(message, throwable);
-        }
-
-        public MaxImagesAcquiredException(Throwable throwable) {
-            super(throwable);
-        }
-    }
+    private static final int ACQUIRE_SUCCESS = 0;
+    /**
+     * Returned by nativeImageSetup when we couldn't acquire the buffer,
+     * because there were no buffers available to acquire.
+     */
+    private static final int ACQUIRE_NO_BUFS = 1;
+    /**
+     * Returned by nativeImageSetup when we couldn't acquire the buffer
+     * because the consumer has already acquired {@maxImages} and cannot
+     * acquire more than that.
+     */
+    private static final int ACQUIRE_MAX_IMAGES = 2;
 
     /**
      * <p>Create a new reader for images of the desired size and format.</p>
@@ -195,7 +172,7 @@
      * </p>
      *
      * <p>Attempting to acquire more than {@code maxImages} concurrently will result in the
-     * acquire function throwing a {@link MaxImagesAcquiredException}. Furthermore,
+     * acquire function throwing a {@link IllegalStateException}. Furthermore,
      * while the max number of images have been acquired by the ImageReader user, the producer
      * enqueueing additional images may stall until at least one image has been released. </p>
      *
@@ -243,26 +220,26 @@
      * {@code (maxImages - currentAcquiredImages < 2)} will not discard as expected.
      * </p>
      * <p>
-     * This operation will fail by throwing an {@link MaxImagesAcquiredException} if
+     * This operation will fail by throwing an {@link IllegalStateException} if
      * {@code maxImages} have been acquired with {@link #acquireLatestImage} or
      * {@link #acquireNextImage}. In particular a sequence of {@link #acquireLatestImage}
      * calls greater than {@link #getMaxImages} without calling {@link Image#close} in-between
-     * will exhaust the underlying queue. At such a time, {@link MaxImagesAcquiredException}
+     * will exhaust the underlying queue. At such a time, {@link IllegalStateException}
      * will be thrown until more images are
      * released with {@link Image#close}.
      * </p>
      *
      * @return latest frame of image data, or {@code null} if no image data is available.
-     * @throws MaxImagesAcquiredException if too many images are currently acquired
+     * @throws IllegalStateException if too many images are currently acquired
      */
-    public Image acquireLatestImage() throws MaxImagesAcquiredException {
+    public Image acquireLatestImage() {
         Image image = acquireNextImage();
         if (image == null) {
             return null;
         }
         try {
             for (;;) {
-                Image next = acquireNextImageNoThrow();
+                Image next = acquireNextImageNoThrowISE();
                 if (next == null) {
                     Image result = image;
                     image = null;
@@ -278,12 +255,48 @@
         }
     }
 
-    private Image acquireNextImageNoThrow() {
-        try {
-            return acquireNextImage();
-        } catch (MaxImagesAcquiredException ex) {
-            return null;
+    /**
+     * Don't throw IllegalStateException if there are too many images acquired.
+     *
+     * @return Image if acquiring succeeded, or null otherwise.
+     *
+     * @hide
+     */
+    public Image acquireNextImageNoThrowISE() {
+        SurfaceImage si = new SurfaceImage();
+        return acquireNextSurfaceImage(si) == ACQUIRE_SUCCESS ? si : null;
+    }
+
+    /**
+     * Attempts to acquire the next image from the underlying native implementation.
+     *
+     * <p>
+     * Note that unexpected failures will throw at the JNI level.
+     * </p>
+     *
+     * @param si A blank SurfaceImage.
+     * @return One of the {@code ACQUIRE_*} codes that determine success or failure.
+     *
+     * @see #ACQUIRE_MAX_IMAGES
+     * @see #ACQUIRE_NO_BUFS
+     * @see #ACQUIRE_SUCCESS
+     */
+    private int acquireNextSurfaceImage(SurfaceImage si) {
+
+        int status = nativeImageSetup(si);
+
+        switch (status) {
+            case ACQUIRE_SUCCESS:
+                si.createSurfacePlanes();
+                si.setImageValid(true);
+            case ACQUIRE_NO_BUFS:
+            case ACQUIRE_MAX_IMAGES:
+                break;
+            default:
+                throw new AssertionError("Unknown nativeImageSetup return code " + status);
         }
+
+        return status;
     }
 
     /**
@@ -301,28 +314,36 @@
      * </p>
      *
      * <p>
-     * This operation will fail by throwing an {@link MaxImagesAcquiredException} if
+     * This operation will fail by throwing an {@link IllegalStateException} if
      * {@code maxImages} have been acquired with {@link #acquireNextImage} or
      * {@link #acquireLatestImage}. In particular a sequence of {@link #acquireNextImage} or
      * {@link #acquireLatestImage} calls greater than {@link #getMaxImages maxImages} without
      * calling {@link Image#close} in-between will exhaust the underlying queue. At such a time,
-     * {@link MaxImagesAcquiredException} will be thrown until more images are released with
+     * {@link IllegalStateException} will be thrown until more images are released with
      * {@link Image#close}.
      * </p>
      *
      * @return a new frame of image data, or {@code null} if no image data is available.
-     * @throws MaxImagesAcquiredException if {@code maxImages} images are currently acquired
+     * @throws IllegalStateException if {@code maxImages} images are currently acquired
      * @see #acquireLatestImage
      */
-    public Image acquireNextImage() throws MaxImagesAcquiredException {
+    public Image acquireNextImage() {
         SurfaceImage si = new SurfaceImage();
-        if (nativeImageSetup(si)) {
-            // create SurfacePlane objects
-            si.createSurfacePlanes();
-            si.setImageValid(true);
-            return si;
+        int status = acquireNextSurfaceImage(si);
+
+        switch (status) {
+            case ACQUIRE_SUCCESS:
+                return si;
+            case ACQUIRE_NO_BUFS:
+                return null;
+            case ACQUIRE_MAX_IMAGES:
+                throw new IllegalStateException(
+                        String.format(
+                                "maxImages (%d) has already been acquired, " +
+                                "call #close before acquiring more.", mMaxImages));
+            default:
+                throw new AssertionError("Unknown nativeImageSetup return code " + status);
         }
-        return null;
     }
 
     /**
@@ -658,7 +679,15 @@
     private synchronized native void nativeClose();
     private synchronized native void nativeReleaseImage(Image i);
     private synchronized native Surface nativeGetSurface();
-    private synchronized native boolean nativeImageSetup(Image i);
+
+    /**
+     * @return A return code {@code ACQUIRE_*}
+     *
+     * @see #ACQUIRE_SUCCESS
+     * @see #ACQUIRE_NO_BUFS
+     * @see #ACQUIRE_MAX_IMAGES
+     */
+    private synchronized native int nativeImageSetup(Image i);
 
     /**
      * We use a class initializer to allow the native code to cache some
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 56ae6b4..e0195c6 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -43,13 +43,16 @@
 
 using namespace android;
 
-static const char* const MaxImagesAcquiredException =
-    "android/media/ImageReader$MaxImagesAcquiredException";
-
 enum {
     IMAGE_READER_MAX_NUM_PLANES = 3,
 };
 
+enum {
+    ACQUIRE_SUCCESS = 0,
+    ACQUIRE_NO_BUFFERS = 1,
+    ACQUIRE_MAX_IMAGES = 2,
+};
+
 static struct {
     jfieldID mNativeContext;
     jmethodID postEventFromNative;
@@ -685,14 +688,14 @@
     ctx->returnLockedBuffer(buffer);
 }
 
-static jboolean ImageReader_imageSetup(JNIEnv* env, jobject thiz,
+static jint ImageReader_imageSetup(JNIEnv* env, jobject thiz,
                                              jobject image)
 {
     ALOGV("%s:", __FUNCTION__);
     JNIImageReaderContext* ctx = ImageReader_getContext(env, thiz);
     if (ctx == NULL) {
         jniThrowRuntimeException(env, "ImageReaderContext is not initialized");
-        return false;
+        return -1;
     }
 
     CpuConsumer* consumer = ctx->getCpuConsumer();
@@ -700,27 +703,22 @@
     if (buffer == NULL) {
         ALOGW("Unable to acquire a lockedBuffer, very likely client tries to lock more than"
             " maxImages buffers");
-        jniThrowException(env, MaxImagesAcquiredException,
-                  "Too many outstanding images, close existing images"
-                  " to be able to acquire more.");
-        return false;
+        return ACQUIRE_MAX_IMAGES;
     }
     status_t res = consumer->lockNextBuffer(buffer);
     if (res != NO_ERROR) {
         if (res != BAD_VALUE /*no buffers*/) {
             if (res == NOT_ENOUGH_DATA) {
-                jniThrowException(env, MaxImagesAcquiredException,
-                          "Too many outstanding images, close existing images"
-                          " to be able to acquire more.");
+                return ACQUIRE_MAX_IMAGES;
             } else {
                 ALOGE("%s Fail to lockNextBuffer with error: %d ",
                       __FUNCTION__, res);
-                jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+                jniThrowExceptionFmt(env, "java/lang/AssertionError",
                           "Unknown error (%d) when we tried to lock buffer.",
                           res);
             }
         }
-        return false;
+        return ACQUIRE_NO_BUFFERS;
     }
 
     // Check if the left-top corner of the crop rect is origin, we currently assume this point is
@@ -730,7 +728,7 @@
         ALOGE("crop left: %d, top = %d", lt.x, lt.y);
         jniThrowException(env, "java/lang/UnsupportedOperationException",
                           "crop left top corner need to at origin");
-        return false;
+        return -1;
     }
 
     // Check if the producer buffer configurations match what ImageReader configured.
@@ -761,6 +759,7 @@
         jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
                 "Producer buffer size: %dx%d, doesn't match ImageReader configured size: %dx%d",
                 outputWidth, outputHeight, imageReaderWidth, imageReaderHeight);
+        return -1;
     }
 
     if (ctx->getBufferFormat() != buffer->format) {
@@ -777,14 +776,14 @@
                 buffer->format, ctx->getBufferFormat());
         jniThrowException(env, "java/lang/UnsupportedOperationException",
                 msg.string());
-        return false;
+        return -1;
     }
     // Set SurfaceImage instance member variables
     Image_setBuffer(env, image, buffer);
     env->SetLongField(image, gSurfaceImageClassInfo.mTimestamp,
             static_cast<jlong>(buffer->timestamp));
 
-    return true;
+    return ACQUIRE_SUCCESS;
 }
 
 static jobject ImageReader_getSurface(JNIEnv* env, jobject thiz)
@@ -855,7 +854,7 @@
     {"nativeInit",             "(Ljava/lang/Object;IIII)V",  (void*)ImageReader_init },
     {"nativeClose",            "()V",                        (void*)ImageReader_close },
     {"nativeReleaseImage",     "(Landroid/media/Image;)V",   (void*)ImageReader_imageRelease },
-    {"nativeImageSetup",       "(Landroid/media/Image;)Z",    (void*)ImageReader_imageSetup },
+    {"nativeImageSetup",       "(Landroid/media/Image;)I",    (void*)ImageReader_imageSetup },
     {"nativeGetSurface",       "()Landroid/view/Surface;",   (void*)ImageReader_getSurface },
 };
 
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ImageReaderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ImageReaderTest.java
index 900fff4..f6cd990 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ImageReaderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ImageReaderTest.java
@@ -23,7 +23,6 @@
 import android.media.Image.Plane;
 import android.media.ImageReader;
 import android.media.ImageReader.OnImageAvailableListener;
-import android.media.ImageReader.MaxImagesAcquiredException;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -67,7 +66,6 @@
         {
             mock(Plane.class);
             mock(OnImageAvailableListener.class);
-            mock(MaxImagesAcquiredException.class);
         }
 
     }
@@ -83,8 +81,9 @@
      * Return null when there is nothing in the image queue.
      */
     @SmallTest
-    public void testGetLatestImageEmpty() throws MaxImagesAcquiredException {
+    public void testGetLatestImageEmpty() {
         when(mReader.acquireNextImage()).thenReturn(null);
+        when(mReader.acquireNextImageNoThrowISE()).thenReturn(null);
         assertEquals(null, mReader.acquireLatestImage());
     }
 
@@ -92,8 +91,9 @@
      * Return the last image from the image queue, close up the rest.
      */
     @SmallTest
-    public void testGetLatestImage1() throws MaxImagesAcquiredException {
-        when(mReader.acquireNextImage()).thenReturn(mImage1).thenReturn(null);
+    public void testGetLatestImage1() {
+        when(mReader.acquireNextImage()).thenReturn(mImage1);
+        when(mReader.acquireNextImageNoThrowISE()).thenReturn(null);
         assertEquals(mImage1, mReader.acquireLatestImage());
         verify(mImage1, never()).close();
     }
@@ -102,8 +102,9 @@
      * Return the last image from the image queue, close up the rest.
      */
     @SmallTest
-    public void testGetLatestImage2() throws MaxImagesAcquiredException {
-        when(mReader.acquireNextImage()).thenReturn(mImage1).thenReturn(mImage2).thenReturn(null);
+    public void testGetLatestImage2() {
+        when(mReader.acquireNextImage()).thenReturn(mImage1);
+        when(mReader.acquireNextImageNoThrowISE()).thenReturn(mImage2).thenReturn(null);
         assertEquals(mImage2, mReader.acquireLatestImage());
         verify(mImage1, atLeastOnce()).close();
         verify(mImage2, never()).close();
@@ -113,10 +114,11 @@
      * Return the last image from the image queue, close up the rest.
      */
     @SmallTest
-    public void testGetLatestImage3() throws MaxImagesAcquiredException {
-        when(mReader.acquireNextImage()).thenReturn(mImage1).thenReturn(mImage2).
-                                         thenReturn(mImage3).
-                                         thenReturn(null);
+    public void testGetLatestImage3() {
+        when(mReader.acquireNextImage()).thenReturn(mImage1);
+        when(mReader.acquireNextImageNoThrowISE()).thenReturn(mImage2).
+                                                   thenReturn(mImage3).
+                                                   thenReturn(null);
         assertEquals(mImage3, mReader.acquireLatestImage());
         verify(mImage1, atLeastOnce()).close();
         verify(mImage2, atLeastOnce()).close();
@@ -124,64 +126,27 @@
     }
 
     /**
-     * Return null if get a MaxImagesAcquiredException with no images in the queue.
+     * Return null if get a IllegalStateException with no images in the queue.
      */
     @SmallTest
-    public void testGetLatestImageTooManyBuffersAcquiredEmpty()  throws MaxImagesAcquiredException {
-        when(mReader.acquireNextImage()).thenThrow(new MaxImagesAcquiredException());
+    public void testGetLatestImageTooManyBuffersAcquiredEmpty()  {
+        when(mReader.acquireNextImage()).thenThrow(new IllegalStateException());
         try {
             mReader.acquireLatestImage();
-            fail("Expected MaxImagesAcquiredException to be thrown");
-        } catch(MaxImagesAcquiredException e) {
+            fail("Expected IllegalStateException to be thrown");
+        } catch(IllegalStateException e) {
         }
     }
 
     /**
-     * Return the last image before we get a MaxImagesAcquiredException. Close up the rest.
-     */
-    @SmallTest
-    public void testGetLatestImageTooManyBuffersAcquired1() throws MaxImagesAcquiredException {
-        when(mReader.acquireNextImage()).thenReturn(mImage1).
-                                         thenThrow(new MaxImagesAcquiredException());
-        assertEquals(mImage1, mReader.acquireLatestImage());
-        verify(mImage1, never()).close();
-    }
-
-    /**
-     * Return the last image before we get a MaxImagesAcquiredException. Close up the rest.
-     */
-    @SmallTest
-    public void testGetLatestImageTooManyBuffersAcquired2() throws MaxImagesAcquiredException {
-
-        when(mReader.acquireNextImage()).thenReturn(mImage1).thenReturn(mImage2).
-                                         thenThrow(new MaxImagesAcquiredException());
-        assertEquals(mImage2, mReader.acquireLatestImage());
-        verify(mImage1, atLeastOnce()).close();
-        verify(mImage2, never()).close();
-    }
-
-    /**
-     * Return the last image before we get a MaxImagesAcquiredException. Close up the rest.
-     */
-    @SmallTest
-    public void testGetLatestImageTooManyBuffersAcquired3() throws MaxImagesAcquiredException {
-        when(mReader.acquireNextImage()).thenReturn(mImage1).thenReturn(mImage2).
-                                         thenReturn(mImage3).
-                                         thenThrow(new MaxImagesAcquiredException());
-        assertEquals(mImage3, mReader.acquireLatestImage());
-        verify(mImage1, atLeastOnce()).close();
-        verify(mImage2, atLeastOnce()).close();
-        verify(mImage3, never()).close();
-    }
-
-    /**
      * All images are cleaned up when we get an unexpected Error.
      */
     @SmallTest
-    public void testGetLatestImageExceptionalError() throws MaxImagesAcquiredException {
-        when(mReader.acquireNextImage()).thenReturn(mImage1).thenReturn(mImage2).
-                                         thenReturn(mImage3).
-                                         thenThrow(new OutOfMemoryError());
+    public void testGetLatestImageExceptionalError() {
+        when(mReader.acquireNextImage()).thenReturn(mImage1);
+        when(mReader.acquireNextImageNoThrowISE()).thenReturn(mImage2).
+                                                   thenReturn(mImage3).
+                                                   thenThrow(new OutOfMemoryError());
         try {
             mReader.acquireLatestImage();
             fail("Impossible");
@@ -197,10 +162,12 @@
      * All images are cleaned up when we get an unexpected RuntimeException.
      */
     @SmallTest
-    public void testGetLatestImageExceptionalRuntime() throws MaxImagesAcquiredException {
-        when(mReader.acquireNextImage()).thenReturn(mImage1).thenReturn(mImage2).
-                                         thenReturn(mImage3).
-                                         thenThrow(new RuntimeException());
+    public void testGetLatestImageExceptionalRuntime() {
+
+        when(mReader.acquireNextImage()).thenReturn(mImage1);
+        when(mReader.acquireNextImageNoThrowISE()).thenReturn(mImage2).
+                                                   thenReturn(mImage3).
+                                                   thenThrow(new RuntimeException());
         try {
             mReader.acquireLatestImage();
             fail("Impossible");