Reducing memory usage of wallpaper picker

> Loading preview bitmap only once, instead of loading it twice at BitmapRegionTileSource and BitmapSource
> Maintaing a weak-set of reusable bitmaps and reusing them for decoding bitmaps
> Loading images on a HandlerThread (instead of AsyncTask) and removing any non-started task before submitting a new task
> Loading inbuild images (from resources) on HandlerThread instead of UIThread
> Freeing up unbound GL textures before binding a new texture.

Bug: 18382606
Change-Id: Ic4ca630dd113ded65d2853eb0d291c9e5823637e
(cherry picked from commit 283c2261bd4440f4108a564cea0f5fc499781213)
diff --git a/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLES20Canvas.java b/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLES20Canvas.java
index 8af1f59..933260b 100644
--- a/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLES20Canvas.java
+++ b/WallpaperPicker/src/com/android/gallery3d/glrenderer/GLES20Canvas.java
@@ -696,6 +696,7 @@
     }
 
     private void prepareTexture(BasicTexture texture, int program, ShaderParameter[] params) {
+        deleteRecycledResources();
         GLES20.glUseProgram(program);
         checkError();
         enableBlending(!texture.isOpaque() || getAlpha() < OPAQUE_ALPHA);
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
index 71c7a16..0ddb79e 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
@@ -25,25 +25,36 @@
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.RectF;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
 import android.util.Log;
 import android.view.Display;
 import android.view.View;
 import android.view.WindowManager;
 import android.widget.Toast;
+
 import com.android.gallery3d.common.BitmapCropTask;
 import com.android.gallery3d.common.BitmapUtils;
 import com.android.gallery3d.common.Utils;
 import com.android.photos.BitmapRegionTileSource;
 import com.android.photos.BitmapRegionTileSource.BitmapSource;
+import com.android.photos.BitmapRegionTileSource.BitmapSource.InBitmapProvider;
+import com.android.photos.views.TiledImageRenderer.TileSource;
 
-public class WallpaperCropActivity extends Activity {
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+public class WallpaperCropActivity extends Activity implements Handler.Callback {
     private static final String LOGTAG = "Launcher3.CropActivity";
 
     protected static final String WALLPAPER_WIDTH_KEY = "wallpaper.width";
@@ -59,13 +70,29 @@
     public static final int MAX_BMAP_IN_INTENT = 750000;
     public static final float WALLPAPER_SCREENS_SPAN = 2f;
 
+    private static final int MSG_LOAD_IMAGE = 1;
+
     protected CropView mCropView;
+    protected View mProgressView;
     protected Uri mUri;
     protected View mSetWallpaperButton;
 
+    private HandlerThread mLoaderThread;
+    private Handler mLoaderHandler;
+    private LoadRequest mCurrentLoadRequest;
+    private byte[] mTempStorageForDecoding = new byte[16 * 1024];
+    // A weak-set of reusable bitmaps
+    private Set<Bitmap> mReusableBitmaps =
+            Collections.newSetFromMap(new WeakHashMap<Bitmap, Boolean>());
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+
+        mLoaderThread = new HandlerThread("wallpaper_loader");
+        mLoaderThread.start();
+        mLoaderHandler = new Handler(mLoaderThread.getLooper(), this);
+
         init();
         if (!enableRotation()) {
             setRequestedOrientation(Configuration.ORIENTATION_PORTRAIT);
@@ -76,6 +103,7 @@
         setContentView(R.layout.wallpaper_cropper);
 
         mCropView = (CropView) findViewById(R.id.cropView);
+        mProgressView = findViewById(R.id.loading);
 
         Intent cropIntent = getIntent();
         final Uri imageUri = cropIntent.getData();
@@ -116,7 +144,7 @@
                 }
             }
         };
-        setCropViewTileSource(bitmapSource, true, false, onLoad);
+        setCropViewTileSource(bitmapSource, true, false, null, onLoad);
     }
 
     @Override
@@ -124,65 +152,134 @@
         if (mCropView != null) {
             mCropView.destroy();
         }
+        if (mLoaderThread != null) {
+            mLoaderThread.quit();
+        }
         super.onDestroy();
     }
 
-    public void setCropViewTileSource(
-            final BitmapRegionTileSource.BitmapSource bitmapSource, final boolean touchEnabled,
-            final boolean moveToLeft, final Runnable postExecute) {
-        final Context context = WallpaperCropActivity.this;
-        final View progressView = findViewById(R.id.loading);
-        final AsyncTask<Void, Void, Void> loadBitmapTask = new AsyncTask<Void, Void, Void>() {
-            protected Void doInBackground(Void...args) {
-                if (!isCancelled()) {
-                    try {
-                        bitmapSource.loadInBackground();
-                    } catch (SecurityException securityException) {
-                        if (isDestroyed()) {
-                            // Temporarily granted permissions are revoked when the activity
-                            // finishes, potentially resulting in a SecurityException here.
-                            // Even though {@link #isDestroyed} might also return true in different
-                            // situations where the configuration changes, we are fine with
-                            // catching these cases here as well.
-                            cancel(false);
-                        } else {
-                            // otherwise it had a different cause and we throw it further
-                            throw securityException;
+    /**
+     * This is called on {@link #mLoaderThread}
+     */
+    @Override
+    public boolean handleMessage(Message msg) {
+        if (msg.what == MSG_LOAD_IMAGE) {
+            final LoadRequest req = (LoadRequest) msg.obj;
+            try {
+                req.src.loadInBackground(new InBitmapProvider() {
+
+                    @Override
+                    public Bitmap forPixelCount(int count) {
+                        synchronized (mReusableBitmaps) {
+                            Iterator<Bitmap> itr = mReusableBitmaps.iterator();
+                            while (itr.hasNext()) {
+                                Bitmap b = itr.next();
+                                if (b.getWidth() * b.getHeight() >= count) {
+                                    itr.remove();
+                                    return b;
+                                }
+                            }
                         }
+                        return null;
                     }
-                }
-                return null;
-            }
-            protected void onPostExecute(Void arg) {
-                if (!isCancelled()) {
-                    progressView.setVisibility(View.INVISIBLE);
-                    if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
-                        mCropView.setTileSource(
-                                new BitmapRegionTileSource(context, bitmapSource), null);
-                        mCropView.setTouchEnabled(touchEnabled);
-                        if (moveToLeft) {
-                            mCropView.moveToLeft();
-                        }
-                    }
-                }
-                if (postExecute != null) {
-                    postExecute.run();
+                });
+            } catch (SecurityException securityException) {
+                if (isDestroyed()) {
+                    // Temporarily granted permissions are revoked when the activity
+                    // finishes, potentially resulting in a SecurityException here.
+                    // Even though {@link #isDestroyed} might also return true in different
+                    // situations where the configuration changes, we are fine with
+                    // catching these cases here as well.
+                    return true;
+                } else {
+                    // otherwise it had a different cause and we throw it further
+                    throw securityException;
                 }
             }
-        };
+
+            req.result = new BitmapRegionTileSource(this, req.src, mTempStorageForDecoding);
+            runOnUiThread(new Runnable() {
+
+                @Override
+                public void run() {
+                    if (req == mCurrentLoadRequest) {
+                        onLoadRequestComplete(req,
+                                req.src.getLoadingState() == BitmapSource.State.LOADED);
+                    } else {
+                        addReusableBitmap(req.result);
+                    }
+                }
+            });
+            return true;
+        }
+        return false;
+    }
+
+    private void addReusableBitmap(TileSource src) {
+        synchronized (mReusableBitmaps) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
+                    && src instanceof BitmapRegionTileSource) {
+                Bitmap preview = ((BitmapRegionTileSource) src).getBitmap();
+                if (preview != null && preview.isMutable()) {
+                    mReusableBitmaps.add(preview);
+                }
+            }
+        }
+    }
+
+    protected void onLoadRequestComplete(LoadRequest req, boolean success) {
+        mCurrentLoadRequest = null;
+        if (success) {
+            TileSource oldSrc = mCropView.getTileSource();
+            mCropView.setTileSource(req.result, null);
+            mCropView.setTouchEnabled(req.touchEnabled);
+            if (req.moveToLeft) {
+                mCropView.moveToLeft();
+            }
+            if (req.scaleProvider != null) {
+                mCropView.setScale(req.scaleProvider.getScale(req.result));
+            }
+
+            // Free last image
+            if (oldSrc != null) {
+                // Call yield instead of recycle, as we only want to free GL resource.
+                // We can still reuse the bitmap for decoding any other image.
+                oldSrc.getPreview().yield();
+            }
+            addReusableBitmap(oldSrc);
+        }
+        if (req.postExecute != null) {
+            req.postExecute.run();
+        }
+    }
+
+    public final void setCropViewTileSource(BitmapSource bitmapSource, boolean touchEnabled,
+            boolean moveToLeft, CropViewScaleProvider scaleProvider, Runnable postExecute) {
+        final LoadRequest req = new LoadRequest();
+        req.moveToLeft = moveToLeft;
+        req.src = bitmapSource;
+        req.touchEnabled = touchEnabled;
+        req.postExecute = postExecute;
+        req.scaleProvider = scaleProvider;
+        mCurrentLoadRequest = req;
+
+        // Remove any pending requests
+        mLoaderHandler.removeMessages(MSG_LOAD_IMAGE);
+        Message.obtain(mLoaderHandler, MSG_LOAD_IMAGE, req).sendToTarget();
+
         // We don't want to show the spinner every time we load an image, because that would be
         // annoying; instead, only start showing the spinner if loading the image has taken
         // longer than 1 sec (ie 1000 ms)
-        progressView.postDelayed(new Runnable() {
+        mProgressView.postDelayed(new Runnable() {
             public void run() {
-                if (loadBitmapTask.getStatus() != AsyncTask.Status.FINISHED) {
-                    progressView.setVisibility(View.VISIBLE);
+                if (mCurrentLoadRequest == req) {
+                    mProgressView.setVisibility(View.VISIBLE);
                 }
             }
         }, 1000);
-        loadBitmapTask.execute();
     }
 
+
     public boolean enableRotation() {
         return getResources().getBoolean(R.bool.allow_rotation);
     }
@@ -372,4 +469,18 @@
             wallpaperManager.suggestDesiredDimensions(savedWidth, savedHeight);
         }
     }
+
+    static class LoadRequest {
+        BitmapSource src;
+        boolean touchEnabled;
+        boolean moveToLeft;
+        Runnable postExecute;
+        CropViewScaleProvider scaleProvider;
+
+        TileSource result;
+    }
+
+    interface CropViewScaleProvider {
+        float getScale(TileSource src);
+    }
 }
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
index f6cc6d0..cbed61b 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
@@ -74,6 +74,7 @@
 import com.android.gallery3d.common.Utils;
 import com.android.photos.BitmapRegionTileSource;
 import com.android.photos.BitmapRegionTileSource.BitmapSource;
+import com.android.photos.views.TiledImageRenderer.TileSource;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -168,7 +169,7 @@
             }
             mBitmapSource = new BitmapRegionTileSource.UriBitmapSource(
                     a, mUri, BitmapRegionTileSource.MAX_PREVIEW_SIZE);
-            a.setCropViewTileSource(mBitmapSource, true, false, onLoad);
+            a.setCropViewTileSource(mBitmapSource, true, false, null, onLoad);
         }
         @Override
         public void onSave(final WallpaperPickerActivity a) {
@@ -205,7 +206,7 @@
         public void onClick(WallpaperPickerActivity a) {
             BitmapRegionTileSource.UriBitmapSource bitmapSource =
                     new BitmapRegionTileSource.UriBitmapSource(a, Uri.fromFile(mFile), 1024);
-            a.setCropViewTileSource(bitmapSource, false, true, null);
+            a.setCropViewTileSource(bitmapSource, false, true, null, null);
         }
         @Override
         public void onSave(WallpaperPickerActivity a) {
@@ -231,22 +232,22 @@
             mThumb = thumb;
         }
         @Override
-        public void onClick(WallpaperPickerActivity a) {
+        public void onClick(final WallpaperPickerActivity a) {
             BitmapRegionTileSource.ResourceBitmapSource bitmapSource =
                     new BitmapRegionTileSource.ResourceBitmapSource(
                             mResources, mResId, BitmapRegionTileSource.MAX_PREVIEW_SIZE);
-            bitmapSource.loadInBackground();
-            BitmapRegionTileSource source = new BitmapRegionTileSource(a, bitmapSource);
-            CropView v = a.getCropView();
-            v.setTileSource(source, null);
-            Point wallpaperSize = BitmapUtils.getDefaultWallpaperSize(
-                    a.getResources(), a.getWindowManager());
-            RectF crop = Utils.getMaxCropRect(
-                    source.getImageWidth(), source.getImageHeight(),
-                    wallpaperSize.x, wallpaperSize.y, false);
-            v.setScale(wallpaperSize.x / crop.width());
-            v.setTouchEnabled(false);
-            a.setSystemWallpaperVisiblity(false);
+            a.setCropViewTileSource(bitmapSource, false, false, new CropViewScaleProvider() {
+
+                @Override
+                public float getScale(TileSource src) {
+                    Point wallpaperSize = BitmapUtils.getDefaultWallpaperSize(
+                            a.getResources(), a.getWindowManager());
+                    RectF crop = Utils.getMaxCropRect(
+                            src.getImageWidth(), src.getImageHeight(),
+                            wallpaperSize.x, wallpaperSize.y, false);
+                    return wallpaperSize.x / crop.width();
+                }
+            }, null);
         }
         @Override
         public void onSave(WallpaperPickerActivity a) {
@@ -271,21 +272,26 @@
         @Override
         public void onClick(WallpaperPickerActivity a) {
             CropView c = a.getCropView();
-
             Drawable defaultWallpaper = WallpaperManager.getInstance(a).getBuiltInDrawable(
                     c.getWidth(), c.getHeight(), false, 0.5f, 0.5f);
-
             if (defaultWallpaper == null) {
                 Log.w(TAG, "Null default wallpaper encountered.");
                 c.setTileSource(null, null);
                 return;
             }
 
-            c.setTileSource(
-                    new DrawableTileSource(a, defaultWallpaper, DrawableTileSource.MAX_PREVIEW_SIZE), null);
-            c.setScale(1f);
-            c.setTouchEnabled(false);
-            a.setSystemWallpaperVisiblity(false);
+            LoadRequest req = new LoadRequest();
+            req.moveToLeft = false;
+            req.touchEnabled = false;
+            req.scaleProvider = new CropViewScaleProvider() {
+
+                @Override
+                public float getScale(TileSource src) {
+                    return 1f;
+                }
+            };
+            req.result = new DrawableTileSource(a, defaultWallpaper, DrawableTileSource.MAX_PREVIEW_SIZE);
+            a.onLoadRequestComplete(req, true);
         }
         @Override
         public void onSave(WallpaperPickerActivity a) {
@@ -348,24 +354,11 @@
     }
 
     @Override
-    public void setCropViewTileSource(BitmapSource bitmapSource,
-                                      boolean touchEnabled,
-                                      boolean moveToLeft,
-                                      final Runnable postExecute) {
-        // we also want to show our own wallpaper instead of the one in the background
-        Runnable showPostExecuteRunnable = new Runnable() {
-            @Override
-            public void run() {
-                if(postExecute != null) {
-                    postExecute.run();
-                }
-                setSystemWallpaperVisiblity(false);
-            }
-        };
-        super.setCropViewTileSource(bitmapSource,
-                touchEnabled,
-                moveToLeft,
-                showPostExecuteRunnable);
+    protected void onLoadRequestComplete(LoadRequest req, boolean success) {
+        super.onLoadRequestComplete(req, success);
+        if (success) {
+            setSystemWallpaperVisiblity(false);
+        }
     }
 
     // called by onCreate; this is subclassed to overwrite WallpaperCropActivity
@@ -375,6 +368,9 @@
         mCropView = (CropView) findViewById(R.id.cropView);
         mCropView.setVisibility(View.INVISIBLE);
 
+        mProgressView = findViewById(R.id.loading);
+
+
         mWallpaperStrip = findViewById(R.id.wallpaper_strip);
         mCropView.setTouchCallback(new CropView.TouchCallback() {
             ViewPropertyAnimator mAnim;
diff --git a/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java b/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java
index 66ece4f..15f97e5 100644
--- a/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java
+++ b/WallpaperPicker/src/com/android/photos/BitmapRegionTileSource.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapRegionDecoder;
 import android.graphics.Canvas;
@@ -28,7 +27,6 @@
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Build;
-import android.os.Build.VERSION_CODES;
 import android.util.Log;
 
 import com.android.gallery3d.common.BitmapUtils;
@@ -148,8 +146,6 @@
 
     private static final String TAG = "BitmapRegionTileSource";
 
-    private static final boolean REUSE_BITMAP =
-            Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
     private static final int GL_SIZE_LIMIT = 2048;
     // This must be no larger than half the size of the GL_SIZE_LIMIT
     // due to decodePreview being allowed to be up to 2x the size of the target
@@ -158,14 +154,14 @@
     public static abstract class BitmapSource {
         private SimpleBitmapRegionDecoder mDecoder;
         private Bitmap mPreview;
-        private int mPreviewSize;
+        private final int mPreviewSize;
         private int mRotation;
         public enum State { NOT_LOADED, LOADED, ERROR_LOADING };
         private State mState = State.NOT_LOADED;
         public BitmapSource(int previewSize) {
-            mPreviewSize = previewSize;
+            mPreviewSize = Math.min(previewSize, MAX_PREVIEW_SIZE);
         }
-        public boolean loadInBackground() {
+        public boolean loadInBackground(InBitmapProvider bitmapProvider) {
             ExifInterface ei = new ExifInterface();
             if (readExif(ei)) {
                 Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
@@ -181,15 +177,33 @@
                 int width = mDecoder.getWidth();
                 int height = mDecoder.getHeight();
                 if (mPreviewSize != 0) {
-                    int previewSize = Math.min(mPreviewSize, MAX_PREVIEW_SIZE);
                     BitmapFactory.Options opts = new BitmapFactory.Options();
                     opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
                     opts.inPreferQualityOverSpeed = true;
 
-                    float scale = (float) previewSize / Math.max(width, height);
+                    float scale = (float) mPreviewSize / Math.max(width, height);
                     opts.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
                     opts.inJustDecodeBounds = false;
-                    mPreview = loadPreviewBitmap(opts);
+                    opts.inMutable = true;
+
+                    if (bitmapProvider != null) {
+                        int expectedPixles = (width / opts.inSampleSize) * (height / opts.inSampleSize);
+                        Bitmap reusableBitmap = bitmapProvider.forPixelCount(expectedPixles);
+                        if (reusableBitmap != null) {
+                            // Try loading with reusable bitmap
+                            opts.inBitmap = reusableBitmap;
+                            try {
+                                mPreview = loadPreviewBitmap(opts);
+                            } catch (IllegalArgumentException e) {
+                                Log.d(TAG, "Unable to reusage bitmap", e);
+                                opts.inBitmap = null;
+                                mPreview = null;
+                            }
+                        }
+                    }
+                    if (mPreview == null) {
+                        mPreview = loadPreviewBitmap(opts);
+                    }
                 }
                 mState = State.LOADED;
                 return true;
@@ -208,10 +222,6 @@
             return mPreview;
         }
 
-        public int getPreviewSize() {
-            return mPreviewSize;
-        }
-
         public int getRotation() {
             return mRotation;
         }
@@ -219,6 +229,10 @@
         public abstract boolean readExif(ExifInterface ei);
         public abstract SimpleBitmapRegionDecoder loadBitmapRegionDecoder();
         public abstract Bitmap loadPreviewBitmap(BitmapFactory.Options options);
+
+        public interface InBitmapProvider {
+            Bitmap forPixelCount(int count);
+        }
     }
 
     public static class FilePathBitmapSource extends BitmapSource {
@@ -306,13 +320,13 @@
                 Utils.closeSilently(is);
                 return true;
             } catch (FileNotFoundException e) {
-                Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
+                Log.d("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
                 return false;
             } catch (IOException e) {
-                Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
+                Log.d("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
                 return false;
             } catch (NullPointerException e) {
-                Log.e("BitmapRegionTileSource", "Failed to read EXIF for URI " + mUri, e);
+                Log.d("BitmapRegionTileSource", "Failed to read EXIF for URI " + mUri, e);
                 return false;
             } finally {
                 Utils.closeSilently(is);
@@ -372,11 +386,9 @@
 
     // For use only by getTile
     private Rect mWantRegion = new Rect();
-    private Rect mOverlapRegion = new Rect();
     private BitmapFactory.Options mOptions;
-    private Canvas mCanvas;
 
-    public BitmapRegionTileSource(Context context, BitmapSource source) {
+    public BitmapRegionTileSource(Context context, BitmapSource source, byte[] tempStorage) {
         mTileSize = TiledImageRenderer.suggestedTileSize(context);
         mRotation = source.getRotation();
         mDecoder = source.getBitmapRegionDecoder();
@@ -386,27 +398,26 @@
             mOptions = new BitmapFactory.Options();
             mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
             mOptions.inPreferQualityOverSpeed = true;
-            mOptions.inTempStorage = new byte[16 * 1024];
-            int previewSize = source.getPreviewSize();
-            if (previewSize != 0) {
-                previewSize = Math.min(previewSize, MAX_PREVIEW_SIZE);
-                // Although this is the same size as the Bitmap that is likely already
-                // loaded, the lifecycle is different and interactions are on a different
-                // thread. Thus to simplify, this source will decode its own bitmap.
-                Bitmap preview = decodePreview(source, previewSize);
-                if (preview.getWidth() <= GL_SIZE_LIMIT && preview.getHeight() <= GL_SIZE_LIMIT) {
+            mOptions.inTempStorage = tempStorage;
+
+            Bitmap preview = source.getPreviewBitmap();
+            if (preview != null &&
+                    preview.getWidth() <= GL_SIZE_LIMIT && preview.getHeight() <= GL_SIZE_LIMIT) {
                     mPreview = new BitmapTexture(preview);
-                } else {
-                    Log.w(TAG, String.format(
-                            "Failed to create preview of apropriate size! "
-                            + " in: %dx%d, out: %dx%d",
-                            mWidth, mHeight,
-                            preview.getWidth(), preview.getHeight()));
-                }
+            } else {
+                Log.w(TAG, String.format(
+                        "Failed to create preview of apropriate size! "
+                        + " in: %dx%d, out: %dx%d",
+                        mWidth, mHeight,
+                        preview.getWidth(), preview.getHeight()));
             }
         }
     }
 
+    public Bitmap getBitmap() {
+        return mPreview instanceof BitmapTexture ? ((BitmapTexture) mPreview).getBitmap() : null;
+    }
+
     @Override
     public int getTileSize() {
         return mTileSize;
@@ -435,10 +446,6 @@
     @Override
     public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
         int tileSize = getTileSize();
-        if (!REUSE_BITMAP) {
-            return getTileWithoutReusingBitmap(level, x, y, tileSize);
-        }
-
         int t = tileSize << level;
         mWantRegion.set(x, y, x + t, y + t);
 
@@ -462,64 +469,4 @@
         }
         return bitmap;
     }
-
-    private Bitmap getTileWithoutReusingBitmap(
-            int level, int x, int y, int tileSize) {
-
-        int t = tileSize << level;
-        mWantRegion.set(x, y, x + t, y + t);
-
-        mOverlapRegion.set(0, 0, mWidth, mHeight);
-
-        mOptions.inSampleSize = (1 << level);
-        Bitmap bitmap = mDecoder.decodeRegion(mOverlapRegion, mOptions);
-
-        if (bitmap == null) {
-            Log.w(TAG, "fail in decoding region");
-        }
-
-        if (mWantRegion.equals(mOverlapRegion)) {
-            return bitmap;
-        }
-
-        Bitmap result = Bitmap.createBitmap(tileSize, tileSize, Config.ARGB_8888);
-        if (mCanvas == null) {
-            mCanvas = new Canvas();
-        }
-        mCanvas.setBitmap(result);
-        mCanvas.drawBitmap(bitmap,
-                (mOverlapRegion.left - mWantRegion.left) >> level,
-                (mOverlapRegion.top - mWantRegion.top) >> level, null);
-        mCanvas.setBitmap(null);
-        return result;
-    }
-
-    /**
-     * Note that the returned bitmap may have a long edge that's longer
-     * than the targetSize, but it will always be less than 2x the targetSize
-     */
-    private Bitmap decodePreview(BitmapSource source, int targetSize) {
-        Bitmap result = source.getPreviewBitmap();
-        if (result == null) {
-            return null;
-        }
-
-        // We need to resize down if the decoder does not support inSampleSize
-        // or didn't support the specified inSampleSize (some decoders only do powers of 2)
-        float scale = (float) targetSize / (float) (Math.max(result.getWidth(), result.getHeight()));
-
-        if (scale <= 0.5) {
-            result = BitmapUtils.resizeBitmapByScale(result, scale, true);
-        }
-        return ensureGLCompatibleBitmap(result);
-    }
-
-    private static Bitmap ensureGLCompatibleBitmap(Bitmap bitmap) {
-        if (bitmap == null || bitmap.getConfig() != null) {
-            return bitmap;
-        }
-        Bitmap newBitmap = bitmap.copy(Config.ARGB_8888, false);
-        bitmap.recycle();
-        return newBitmap;
-    }
 }
diff --git a/WallpaperPicker/src/com/android/photos/views/TiledImageView.java b/WallpaperPicker/src/com/android/photos/views/TiledImageView.java
index 524fa2e..56ee7a6 100644
--- a/WallpaperPicker/src/com/android/photos/views/TiledImageView.java
+++ b/WallpaperPicker/src/com/android/photos/views/TiledImageView.java
@@ -125,6 +125,10 @@
         invalidate();
     }
 
+    public TileSource getTileSource() {
+        return mRenderer.source;
+    }
+
     @Override
     protected void onLayout(boolean changed, int left, int top, int right,
             int bottom) {