Merge "Bitmapfun Sample: Add additional sample images." into jb-mr1-dev
diff --git a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageCache.java b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageCache.java
index f567e76..d65d655 100644
--- a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageCache.java
+++ b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageCache.java
@@ -20,8 +20,8 @@
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.CompressFormat;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.StatFs;
@@ -33,11 +33,16 @@
 import com.example.android.bitmapfun.BuildConfig;
 
 import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.lang.ref.SoftReference;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.util.HashSet;
+import java.util.Iterator;
 
 /**
  * This class holds our bitmap caches (memory and disk).
@@ -68,6 +73,8 @@
     private final Object mDiskCacheLock = new Object();
     private boolean mDiskCacheStarting = true;
 
+    private HashSet<SoftReference<Bitmap>> mReusableBitmaps;
+
     /**
      * Creating a new ImageCache object using the specified parameters.
      *
@@ -126,6 +133,12 @@
             if (BuildConfig.DEBUG) {
                 Log.d(TAG, "Memory cache created (size = " + mCacheParams.memCacheSize + ")");
             }
+
+            // If we're running on Honeycomb or newer, then
+            if (Utils.hasHoneycomb()) {
+                mReusableBitmaps = new HashSet<SoftReference<Bitmap>>();
+            }
+
             mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {
 
                 /**
@@ -138,6 +151,14 @@
                         // The removed entry is a recycling drawable, so notify it 
                         // that it has been removed from the memory cache
                         ((RecyclingBitmapDrawable) oldValue).setIsCached(false);
+                    } else {
+                        // The removed entry is a standard BitmapDrawable
+
+                        if (Utils.hasHoneycomb()) {
+                            // We're running on Honeycomb or later, so add the bitmap
+                            // to a SoftRefrence set for possible use with inBitmap later
+                            mReusableBitmaps.add(new SoftReference<Bitmap>(oldValue.getBitmap()));
+                        }
                     }
                 }
 
@@ -277,6 +298,8 @@
      */
     public Bitmap getBitmapFromDiskCache(String data) {
         final String key = hashKeyForDisk(data);
+        Bitmap bitmap = null;
+
         synchronized (mDiskCacheLock) {
             while (mDiskCacheStarting) {
                 try {
@@ -293,8 +316,12 @@
                         }
                         inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
                         if (inputStream != null) {
-                            final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
-                            return bitmap;
+                            FileDescriptor fd = ((FileInputStream) inputStream).getFD();
+
+                            // Decode bitmap, but we don't want to sample so give
+                            // MAX_VALUE as the target dimensions
+                            bitmap = ImageResizer.decodeSampledBitmapFromDescriptor(
+                                    fd, Integer.MAX_VALUE, Integer.MAX_VALUE, this);
                         }
                     }
                 } catch (final IOException e) {
@@ -307,11 +334,44 @@
                     } catch (IOException e) {}
                 }
             }
-            return null;
+            return bitmap;
         }
     }
 
     /**
+     * @param options - BitmapFactory.Options with out* options populated
+     * @return Bitmap that case be used for inBitmap
+     */
+    protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
+        Bitmap bitmap = null;
+
+        if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
+            final Iterator<SoftReference<Bitmap>> iterator = mReusableBitmaps.iterator();
+            Bitmap item;
+
+            while (iterator.hasNext()) {
+                item = iterator.next().get();
+
+                if (null != item && item.isMutable()) {
+                    // Check to see it the item can be used for inBitmap
+                    if (canUseForInBitmap(item, options)) {
+                        bitmap = item;
+
+                        // Remove from reusable set so it can't be used again
+                        iterator.remove();
+                        break;
+                    }
+                } else {
+                    // Remove from the set if the reference has been cleared.
+                    iterator.remove();
+                }
+            }
+        }
+
+        return bitmap;
+    }
+
+    /**
      * Clears both the memory and disk cache associated with this ImageCache object. Note that
      * this includes disk access so this should not be executed on the main/UI thread.
      */
@@ -426,6 +486,20 @@
     }
 
     /**
+     * @param candidate - Bitmap to check
+     * @param targetOptions - Options that have the out* value populated
+     * @return true if <code>candidate</code> can be used for inBitmap re-use with
+     *      <code>targetOptions</code>
+     */
+    private static boolean canUseForInBitmap(
+            Bitmap candidate, BitmapFactory.Options targetOptions) {
+        int width = targetOptions.outWidth / targetOptions.inSampleSize;
+        int height = targetOptions.outHeight / targetOptions.inSampleSize;
+
+        return candidate.getWidth() == width && candidate.getHeight() == height;
+    }
+
+    /**
      * Get a usable cache directory (external if available, internal otherwise).
      *
      * @param context The context to use
diff --git a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageFetcher.java b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageFetcher.java
index 7084845..4c92d74 100644
--- a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageFetcher.java
+++ b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageFetcher.java
@@ -241,7 +241,8 @@
 
         Bitmap bitmap = null;
         if (fileDescriptor != null) {
-            bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, mImageWidth, mImageHeight);
+            bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, mImageWidth,
+                    mImageHeight, getImageCache());
         }
         if (fileInputStream != null) {
             try {
diff --git a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageResizer.java b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageResizer.java
index 6a129c3..2a9d152 100644
--- a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageResizer.java
+++ b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageResizer.java
@@ -16,10 +16,12 @@
 
 package com.example.android.bitmapfun.util;
 
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.os.Build;
 import android.util.Log;
 
 import com.example.android.bitmapfun.BuildConfig;
@@ -90,7 +92,8 @@
         if (BuildConfig.DEBUG) {
             Log.d(TAG, "processBitmap - " + resId);
         }
-        return decodeSampledBitmapFromResource(mResources, resId, mImageWidth, mImageHeight);
+        return decodeSampledBitmapFromResource(mResources, resId, mImageWidth,
+                mImageHeight, getImageCache());
     }
 
     @Override
@@ -105,11 +108,12 @@
      * @param resId The resource id of the image data
      * @param reqWidth The requested width of the resulting bitmap
      * @param reqHeight The requested height of the resulting bitmap
+     * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap
      * @return A bitmap sampled down from the original with the same aspect ratio and dimensions
      *         that are equal to or greater than the requested width and height
      */
     public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
-            int reqWidth, int reqHeight) {
+            int reqWidth, int reqHeight, ImageCache cache) {
 
         // First decode with inJustDecodeBounds=true to check dimensions
         final BitmapFactory.Options options = new BitmapFactory.Options();
@@ -119,6 +123,11 @@
         // Calculate inSampleSize
         options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
 
+        // If we're running on Honeycomb or newer, try to use inBitmap
+        if (Utils.hasHoneycomb()) {
+            addInBitmapOptions(options, cache);
+        }
+
         // Decode bitmap with inSampleSize set
         options.inJustDecodeBounds = false;
         return BitmapFactory.decodeResource(res, resId, options);
@@ -130,11 +139,12 @@
      * @param filename The full path of the file to decode
      * @param reqWidth The requested width of the resulting bitmap
      * @param reqHeight The requested height of the resulting bitmap
+     * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap
      * @return A bitmap sampled down from the original with the same aspect ratio and dimensions
      *         that are equal to or greater than the requested width and height
      */
     public static Bitmap decodeSampledBitmapFromFile(String filename,
-            int reqWidth, int reqHeight) {
+            int reqWidth, int reqHeight, ImageCache cache) {
 
         // First decode with inJustDecodeBounds=true to check dimensions
         final BitmapFactory.Options options = new BitmapFactory.Options();
@@ -144,6 +154,11 @@
         // Calculate inSampleSize
         options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
 
+        // If we're running on Honeycomb or newer, try to use inBitmap
+        if (Utils.hasHoneycomb()) {
+            addInBitmapOptions(options, cache);
+        }
+
         // Decode bitmap with inSampleSize set
         options.inJustDecodeBounds = false;
         return BitmapFactory.decodeFile(filename, options);
@@ -155,11 +170,12 @@
      * @param fileDescriptor The file descriptor to read from
      * @param reqWidth The requested width of the resulting bitmap
      * @param reqHeight The requested height of the resulting bitmap
+     * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap
      * @return A bitmap sampled down from the original with the same aspect ratio and dimensions
      *         that are equal to or greater than the requested width and height
      */
     public static Bitmap decodeSampledBitmapFromDescriptor(
-            FileDescriptor fileDescriptor, int reqWidth, int reqHeight) {
+            FileDescriptor fileDescriptor, int reqWidth, int reqHeight, ImageCache cache) {
 
         // First decode with inJustDecodeBounds=true to check dimensions
         final BitmapFactory.Options options = new BitmapFactory.Options();
@@ -171,9 +187,34 @@
 
         // Decode bitmap with inSampleSize set
         options.inJustDecodeBounds = false;
+
+        // If we're running on Honeycomb or newer, try to use inBitmap
+        if (Utils.hasHoneycomb()) {
+            addInBitmapOptions(options, cache);
+        }
+
         return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
     }
 
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) {
+        // inBitmap only works with mutable bitmaps so force the decoder to
+        // return mutable bitmaps.
+        options.inMutable = true;
+
+        if (cache != null) {
+            // Try and find a bitmap to use for inBitmap
+            Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
+
+            if (inBitmap != null) {
+                if (BuildConfig.DEBUG) {
+                    Log.d(TAG, "Found bitmap to use for inBitmap");
+                }
+                options.inBitmap = inBitmap;
+            }
+        }
+    }
+
     /**
      * Calculate an inSampleSize for use in a {@link BitmapFactory.Options} object when decoding
      * bitmaps using the decode* methods from {@link BitmapFactory}. This implementation calculates
diff --git a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageWorker.java b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageWorker.java
index 84a0f59..87b2cb2 100644
--- a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageWorker.java
+++ b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageWorker.java
@@ -165,6 +165,13 @@
     protected abstract Bitmap processBitmap(Object data);
 
     /**
+     * @return The {@link ImageCache} object currently being used by this ImageWorker.
+     */
+    protected ImageCache getImageCache() {
+        return mImageCache;
+    }
+
+    /**
      * Cancels any pending work attached to the provided ImageView.
      * @param imageView
      */