Support larger bitmaps in BitmapFactory.Options.inBitmap

bug:8121994

Adds a new distiction between bitmap size and the allocation
(pixel ref/buffer) used to store its data.

BitmapFactory.inBitmap will allow a bitmap to be reinitialized with
new data if the bitmap being decoded is (after sampleSize) equal or
smaller.

Change-Id: I747750a735c858882df3af74fca6cdc46f2a9f81
diff --git a/api/current.txt b/api/current.txt
index 96a8481..08008667e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8633,6 +8633,7 @@
     method public void eraseColor(int);
     method public android.graphics.Bitmap extractAlpha();
     method public android.graphics.Bitmap extractAlpha(android.graphics.Paint, int[]);
+    method public final int getAllocationByteCount();
     method public final int getByteCount();
     method public final android.graphics.Bitmap.Config getConfig();
     method public int getDensity();
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index daabce3..d6549a1 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -211,19 +211,17 @@
 
     SkBitmap* bitmap;
     bool useExistingBitmap = false;
+    unsigned int existingBufferSize = 0;
     if (javaBitmap == NULL) {
         bitmap = new SkBitmap;
     } else {
-        if (sampleSize != 1) {
-            return nullObjectReturn("SkImageDecoder: Cannot reuse bitmap with sampleSize != 1");
-        }
-
         bitmap = (SkBitmap*) env->GetIntField(javaBitmap, gBitmap_nativeBitmapFieldID);
-        // only reuse the provided bitmap if it is immutable
+        // only reuse the provided bitmap if it is mutable
         if (!bitmap->isImmutable()) {
             useExistingBitmap = true;
             // config of supplied bitmap overrules config set in options
             prefConfig = bitmap->getConfig();
+            existingBufferSize = GraphicsJNI::getBitmapAllocationByteCount(env, javaBitmap);
         } else {
             ALOGW("Unable to reuse an immutable bitmap as an image decoder target.");
             bitmap = new SkBitmap;
@@ -252,6 +250,26 @@
         decodeMode = SkImageDecoder::kDecodeBounds_Mode;
     }
 
+    if (javaBitmap != NULL) {
+        // If we're reusing the pixelref from an existing bitmap, decode the bounds and
+        // reinitialize the native object for the new content, keeping the pixelRef
+        SkPixelRef* pixelRef = bitmap->pixelRef();
+        SkSafeRef(pixelRef);
+
+        SkBitmap boundsBitmap;
+        decoder->decode(stream, &boundsBitmap, prefConfig, SkImageDecoder::kDecodeBounds_Mode);
+        stream->rewind();
+
+        if (boundsBitmap.getSize() > existingBufferSize) {
+            return nullObjectReturn("bitmap marked for reuse too small to contain decoded data");
+        }
+
+        bitmap->setConfig(boundsBitmap.config(), boundsBitmap.width(), boundsBitmap.height(), 0);
+        bitmap->setPixelRef(pixelRef);
+        SkSafeUnref(pixelRef);
+        GraphicsJNI::reinitBitmap(env, javaBitmap);
+    }
+
     SkBitmap* decoded;
     if (willScale) {
         decoded = new SkBitmap;
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 191b54a..7c420ad 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -152,6 +152,8 @@
 static jclass   gBitmap_class;
 static jfieldID gBitmap_nativeInstanceID;
 static jmethodID gBitmap_constructorMethodID;
+static jmethodID gBitmap_reinitMethodID;
+static jmethodID gBitmap_getAllocationByteCountMethodID;
 
 static jclass   gBitmapConfig_class;
 static jfieldID gBitmapConfig_nativeInstanceID;
@@ -363,6 +365,15 @@
     return createBitmap(env, bitmap, NULL, isMutable, ninepatch, NULL, density);
 }
 
+void GraphicsJNI::reinitBitmap(JNIEnv* env, jobject javaBitmap)
+{
+    env->CallVoidMethod(javaBitmap, gBitmap_reinitMethodID);
+}
+
+int GraphicsJNI::getBitmapAllocationByteCount(JNIEnv* env, jobject javaBitmap)
+{
+    return env->CallIntMethod(javaBitmap, gBitmap_getAllocationByteCountMethodID);
+}
 
 jobject GraphicsJNI::createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap)
 {
@@ -584,6 +595,8 @@
     gBitmap_nativeInstanceID = getFieldIDCheck(env, gBitmap_class, "mNativeBitmap", "I");
     gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>",
                                             "(I[BZ[B[II)V");
+    gBitmap_reinitMethodID = env->GetMethodID(gBitmap_class, "reinit", "()V");
+    gBitmap_getAllocationByteCountMethodID = env->GetMethodID(gBitmap_class, "getAllocationByteCount", "()I");
     gBitmapRegionDecoder_class = make_globalref(env, "android/graphics/BitmapRegionDecoder");
     gBitmapRegionDecoder_constructorMethodID = env->GetMethodID(gBitmapRegionDecoder_class, "<init>", "(I)V");
 
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index c5b06f5..69b67cb 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -59,6 +59,10 @@
     static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, bool isMutable,
                                 jbyteArray ninepatch, int density = -1);
 
+    static void reinitBitmap(JNIEnv* env, jobject javaBitmap);
+
+    static int getBitmapAllocationByteCount(JNIEnv* env, jobject javaBitmap);
+
     static jobject createRegion(JNIEnv* env, SkRegion* region);
 
     static jobject createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap);
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index d26b5a2..106f4bd 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -85,26 +85,20 @@
     }
 
     /**
-     * @noinspection UnusedDeclaration
+     * Private constructor that must received an already allocated native
+     * bitmap int (pointer).
      */
-    /*  Private constructor that must received an already allocated native
-        bitmap int (pointer).
-
-        This can be called from JNI code.
-    */
+    @SuppressWarnings({"UnusedDeclaration"}) // called from JNI
     Bitmap(int nativeBitmap, byte[] buffer, boolean isMutable, byte[] ninePatchChunk,
             int density) {
         this(nativeBitmap, buffer, isMutable, ninePatchChunk, null, density);
     }
 
     /**
-     * @noinspection UnusedDeclaration
+     * Private constructor that must received an already allocated native bitmap
+     * int (pointer).
      */
-    /*  Private constructor that must received an already allocated native
-        bitmap int (pointer).
-
-        This can be called from JNI code.
-    */
+    @SuppressWarnings({"UnusedDeclaration"}) // called from JNI
     Bitmap(int nativeBitmap, byte[] buffer, boolean isMutable, byte[] ninePatchChunk,
             int[] layoutBounds, int density) {
         if (nativeBitmap == 0) {
@@ -125,6 +119,14 @@
     }
 
     /**
+     * Native bitmap has been reconfigured, so discard cached width/height
+     */
+    @SuppressWarnings({"UnusedDeclaration"}) // called from JNI
+    void reinit() {
+        mWidth = mHeight = -1;
+    }
+
+    /**
      * <p>Returns the density for this bitmap.</p>
      *
      * <p>The default density is the same density as the current display,
@@ -454,7 +456,7 @@
     /**
      * Creates a new bitmap, scaled from an existing bitmap, when possible. If the
      * specified width and height are the same as the current width and height of 
-     * the source btimap, the source bitmap is returned and now new bitmap is
+     * the source bitmap, the source bitmap is returned and no new bitmap is
      * created.
      *
      * @param src       The source bitmap.
@@ -989,6 +991,10 @@
      * getPixels() or setPixels(), then the pixels are uniformly treated as
      * 32bit values, packed according to the Color class.
      *
+     * <p>As of {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE}, this method
+     * should not be used to calculate the memory usage of the bitmap. Instead,
+     * see {@link #getAllocationByteCount()}.
+     *
      * @return number of bytes between rows of the native bitmap pixels.
      */
     public final int getRowBytes() {
@@ -996,7 +1002,11 @@
     }
 
     /**
-     * Returns the number of bytes used to store this bitmap's pixels.
+     * Returns the minimum number of bytes that can be used to store this bitmap's pixels.
+     *
+     * <p>As of {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE}, the result of this method can
+     * no longer be used to determine memory usage of a bitmap. See {@link
+     * #getAllocationByteCount()}.
      */
     public final int getByteCount() {
         // int result permits bitmaps up to 46,340 x 46,340
@@ -1004,6 +1014,20 @@
     }
 
     /**
+     * Returns the size of the allocated memory used to store this bitmap's pixels.
+     *
+     * <p>This can be larger than the result of {@link #getByteCount()} if a bitmap is reused to
+     * decode other bitmaps of smaller size. See {@link BitmapFactory.Options#inBitmap inBitmap in
+     * BitmapFactory.Options}. If a bitmap is not reused in this way, this value will be the same as
+     * that returned by {@link #getByteCount()}.
+     *
+     * <p>This value will not change over the lifetime of a Bitmap.
+     */
+    public final int getAllocationByteCount() {
+        return mBuffer.length;
+    }
+
+    /**
      * If the bitmap's internal config is in one of the public formats, return
      * that config, otherwise return null.
      */
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index f155cd2..78ee7c6 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -50,13 +50,22 @@
          * reuse this bitmap when loading content. If the decode operation cannot
          * use this bitmap, the decode method will return <code>null</code> and
          * will throw an IllegalArgumentException. The current implementation
-         * necessitates that the reused bitmap be mutable and of the same size as the
-         * source content. The source content must be in jpeg or png format (whether as
-         * a resource or as a stream). The {@link android.graphics.Bitmap.Config
-         * configuration} of the reused bitmap will override the setting of
-         * {@link #inPreferredConfig}, if set. The reused bitmap will continue to
-         * remain mutable even when decoding a resource which would normally result
-         * in an immutable bitmap.
+         * necessitates that the reused bitmap be mutable and of a size that is
+         * equal to or larger than the source content. The source content must be
+         * in jpeg or png format (whether as a resource or as a stream). The
+         * {@link android.graphics.Bitmap.Config configuration} of the reused
+         * bitmap will override the setting of {@link #inPreferredConfig}, if set.
+         * The reused bitmap will continue to remain mutable even when decoding a
+         * resource which would normally result in an immutable bitmap.
+         *
+         * <p>As of {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE}, the reused
+         * bitmap can be used to decode any other bitmaps as long as the resulting
+         * {@link Bitmap#getByteCount() byte count} of the decoded bitmap is less
+         * than or equal to the {@link Bitmap#getAllocationByteCount() allocated byte count}
+         * of the reused bitmap. This can be because the intrinsic size is smaller,
+         * or the sampled size is smaller. Prior to
+         * {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE}, only equal sized
+         * bitmaps are supported, with {@link #inSampleSize} set to 1.
          *
          * <p>You should still always use the returned Bitmap of the decode
          * method and not assume that reusing the bitmap worked, due to the