From b9ab10253ab04bacd7d27ab7a7aecd561f7efbc2 Mon Sep 17 00:00:00 2001
From: daniel oeh <daniel.oeh@gmail.com>
Date: Wed, 19 Mar 2014 18:01:55 +0100
Subject: [PATCH] Prevent images from being loaded multiple times

(copied from AntennaPodSP)
---
 .../antennapod/asynctask/ImageLoader.java     | 372 +++++++++---------
 1 file changed, 190 insertions(+), 182 deletions(-)

diff --git a/src/de/danoeh/antennapod/asynctask/ImageLoader.java b/src/de/danoeh/antennapod/asynctask/ImageLoader.java
index a4a9bc823..c49d7549b 100644
--- a/src/de/danoeh/antennapod/asynctask/ImageLoader.java
+++ b/src/de/danoeh/antennapod/asynctask/ImageLoader.java
@@ -1,14 +1,8 @@
 package de.danoeh.antennapod.asynctask;
 
-import java.io.InputStream;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.content.Context;
-import android.content.res.TypedArray;
 import android.os.Handler;
 import android.support.v4.util.LruCache;
 import android.util.Log;
@@ -17,214 +11,228 @@ import de.danoeh.antennapod.AppConfig;
 import de.danoeh.antennapod.PodcastApp;
 import de.danoeh.antennapod.R;
 
-/** Caches and loads FeedImage bitmaps in the background */
+import java.io.InputStream;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * Caches and loads FeedImage bitmaps in the background
+ */
 public class ImageLoader {
-	private static final String TAG = "ImageLoader";
-	private static ImageLoader singleton;
+    private static final String TAG = "ImageLoader";
+    private static ImageLoader singleton;
 
-	public static final int IMAGE_TYPE_THUMBNAIL = 0;
-	public static final int IMAGE_TYPE_COVER = 1;
+    public static final int IMAGE_TYPE_THUMBNAIL = 0;
+    public static final int IMAGE_TYPE_COVER = 1;
 
-	private Handler handler;
-	private ExecutorService executor;
+    private Handler handler;
+    private ExecutorService executor;
 
-	/**
-	 * Stores references to loaded bitmaps. Bitmaps can be accessed by the id of
-	 * the FeedImage the bitmap belongs to.
-	 */
+    /**
+     * Stores references to loaded bitmaps. Bitmaps can be accessed by the id of
+     * the FeedImage the bitmap belongs to.
+     */
 
-	final int memClass = ((ActivityManager) PodcastApp.getInstance()
-			.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
+    final int memClass = ((ActivityManager) PodcastApp.getInstance()
+            .getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
 
-	// Use 1/8th of the available memory for this memory cache.
-	final int thumbnailCacheSize = 1024 * 1024 * memClass / 8;
+    // Use 1/8th of the available memory for this memory cache.
+    final int thumbnailCacheSize = 1024 * 1024 * memClass / 8;
 
-	private LruCache<String, CachedBitmap> coverCache;
-	private LruCache<String, CachedBitmap> thumbnailCache;
+    private LruCache<String, CachedBitmap> coverCache;
+    private LruCache<String, CachedBitmap> thumbnailCache;
 
-	private ImageLoader() {
-		handler = new Handler();
-		executor = createExecutor();
+    private ImageLoader() {
+        handler = new Handler();
+        executor = createExecutor();
 
-		coverCache = new LruCache<String, CachedBitmap>(1);
+        coverCache = new LruCache<String, CachedBitmap>(1);
 
-		thumbnailCache = new LruCache<String, CachedBitmap>(thumbnailCacheSize) {
+        thumbnailCache = new LruCache<String, CachedBitmap>(thumbnailCacheSize) {
 
-			@SuppressLint("NewApi")
-			@Override
-			protected int sizeOf(String key, CachedBitmap value) {
-				if (Integer.valueOf(android.os.Build.VERSION.SDK_INT) >= 12)
-					return value.getBitmap().getByteCount();
-				else
-					return (value.getBitmap().getRowBytes() * value.getBitmap()
-							.getHeight());
+            @SuppressLint("NewApi")
+            @Override
+            protected int sizeOf(String key, CachedBitmap value) {
+                if (Integer.valueOf(android.os.Build.VERSION.SDK_INT) >= 12)
+                    return value.getBitmap().getByteCount();
+                else
+                    return (value.getBitmap().getRowBytes() * value.getBitmap()
+                            .getHeight());
 
-			}
+            }
 
-		};
-	}
+        };
+    }
 
-	private ExecutorService createExecutor() {
-		return Executors.newFixedThreadPool(Runtime.getRuntime()
-				.availableProcessors(), new ThreadFactory() {
+    private ExecutorService createExecutor() {
+        return Executors.newFixedThreadPool(Runtime.getRuntime()
+                .availableProcessors(), new ThreadFactory() {
 
-			@Override
-			public Thread newThread(Runnable r) {
-				Thread t = new Thread(r);
-				t.setPriority(Thread.MIN_PRIORITY);
-				return t;
-			}
-		});
-	}
+            @Override
+            public Thread newThread(Runnable r) {
+                Thread t = new Thread(r);
+                t.setPriority(Thread.MIN_PRIORITY);
+                return t;
+            }
+        });
+    }
 
-	public static synchronized ImageLoader getInstance() {
-		if (singleton == null) {
-			singleton = new ImageLoader();
-		}
-		return singleton;
-	}
+    public static synchronized ImageLoader getInstance() {
+        if (singleton == null) {
+            singleton = new ImageLoader();
+        }
+        return singleton;
+    }
 
-	/**
-	 * Load a bitmap from the cover cache. If the bitmap is not in the cache, it
-	 * will be loaded from the disk. This method should either be called if the
-	 * ImageView's size has already been set or inside a Runnable which is
-	 * posted to the ImageView's message queue.
-	 */
-	public void loadCoverBitmap(ImageWorkerTaskResource source, ImageView target) {
-		loadCoverBitmap(source, target, target.getHeight());
-	}
+    /**
+     * Load a bitmap from the cover cache. If the bitmap is not in the cache, it
+     * will be loaded from the disk. This method should either be called if the
+     * ImageView's size has already been set or inside a Runnable which is
+     * posted to the ImageView's message queue.
+     */
+    public void loadCoverBitmap(ImageWorkerTaskResource source, ImageView target) {
+        loadCoverBitmap(source, target, target.getHeight());
+    }
 
-	/**
-	 * Load a bitmap from the cover cache. If the bitmap is not in the cache, it
-	 * will be loaded from the disk. This method should either be called if the
-	 * ImageView's size has already been set or inside a Runnable which is
-	 * posted to the ImageView's message queue.
-	 */
-	public void loadCoverBitmap(ImageWorkerTaskResource source,
-			ImageView target, int length) {
-		final int defaultCoverResource = getDefaultCoverResource(target
-				.getContext());
+    /**
+     * Load a bitmap from the cover cache. If the bitmap is not in the cache, it
+     * will be loaded from the disk. This method should either be called if the
+     * ImageView's size has already been set or inside a Runnable which is
+     * posted to the ImageView's message queue.
+     */
+    public void loadCoverBitmap(ImageWorkerTaskResource source,
+                                ImageView target, int length) {
+        final int defaultCoverResource = getDefaultCoverResource(target
+                .getContext());
+        final String cacheKey;
+        if (source != null && (cacheKey = source.getImageLoaderCacheKey()) != null) {
+            final Object currentTag = target.getTag(R.id.imageloader_key);
+            if (currentTag == null || !cacheKey.equals(currentTag)) {
+                target.setTag(R.id.imageloader_key, cacheKey);
+                CachedBitmap cBitmap = getBitmapFromCoverCache(cacheKey);
+                if (cBitmap != null && cBitmap.getLength() >= length) {
+                    target.setImageBitmap(cBitmap.getBitmap());
+                } else {
+                    target.setImageResource(defaultCoverResource);
+                    BitmapDecodeWorkerTask worker = new BitmapDecodeWorkerTask(
+                            handler, target, source, length, IMAGE_TYPE_COVER);
+                    executor.submit(worker);
+                }
+            }
+        } else {
+            target.setImageResource(defaultCoverResource);
+        }
+    }
 
-		if (source != null && source.getImageLoaderCacheKey() != null) {
-            target.setTag(R.id.imageloader_key, source.getImageLoaderCacheKey());
-            CachedBitmap cBitmap = getBitmapFromCoverCache(source.getImageLoaderCacheKey());
-			if (cBitmap != null && cBitmap.getLength() >= length) {
-				target.setImageBitmap(cBitmap.getBitmap());
-			} else {
-				target.setImageResource(defaultCoverResource);
-				BitmapDecodeWorkerTask worker = new BitmapDecodeWorkerTask(
-						handler, target, source, length, IMAGE_TYPE_COVER);
-				executor.submit(worker);
-			}
-		} else {
-			target.setImageResource(defaultCoverResource);
-		}
-	}
+    /**
+     * Load a bitmap from the thumbnail cache. If the bitmap is not in the
+     * cache, it will be loaded from the disk. This method should either be
+     * called if the ImageView's size has already been set or inside a Runnable
+     * which is posted to the ImageView's message queue.
+     */
+    public void loadThumbnailBitmap(ImageWorkerTaskResource source,
+                                    ImageView target) {
+        loadThumbnailBitmap(source, target, target.getHeight());
+    }
 
-	/**
-	 * Load a bitmap from the thumbnail cache. If the bitmap is not in the
-	 * cache, it will be loaded from the disk. This method should either be
-	 * called if the ImageView's size has already been set or inside a Runnable
-	 * which is posted to the ImageView's message queue.
-	 */
-	public void loadThumbnailBitmap(ImageWorkerTaskResource source,
-			ImageView target) {
-		loadThumbnailBitmap(source, target, target.getHeight());
-	}
+    /**
+     * Load a bitmap from the thumbnail cache. If the bitmap is not in the
+     * cache, it will be loaded from the disk. This method should either be
+     * called if the ImageView's size has already been set or inside a Runnable
+     * which is posted to the ImageView's message queue.
+     */
+    public void loadThumbnailBitmap(ImageWorkerTaskResource source,
+                                    ImageView target, int length) {
+        final int defaultCoverResource = getDefaultCoverResource(target
+                .getContext());
+        final String cacheKey;
+        if (source != null && (cacheKey = source.getImageLoaderCacheKey()) != null) {
+            final Object currentTag = target.getTag(R.id.imageloader_key);
+            if (currentTag == null || !cacheKey.equals(currentTag)) {
+                target.setTag(R.id.imageloader_key, cacheKey);
+                CachedBitmap cBitmap = getBitmapFromThumbnailCache(cacheKey);
+                if (cBitmap != null && cBitmap.getLength() >= length) {
+                    target.setImageBitmap(cBitmap.getBitmap());
+                } else {
+                    target.setImageResource(defaultCoverResource);
+                    BitmapDecodeWorkerTask worker = new BitmapDecodeWorkerTask(
+                            handler, target, source, length, IMAGE_TYPE_THUMBNAIL);
+                    executor.submit(worker);
+                }
+            }
+        } else {
+            target.setImageResource(defaultCoverResource);
+        }
+    }
 
-	/**
-	 * Load a bitmap from the thumbnail cache. If the bitmap is not in the
-	 * cache, it will be loaded from the disk. This method should either be
-	 * called if the ImageView's size has already been set or inside a Runnable
-	 * which is posted to the ImageView's message queue.
-	 */
-	public void loadThumbnailBitmap(ImageWorkerTaskResource source,
-			ImageView target, int length) {
-		final int defaultCoverResource = getDefaultCoverResource(target
-				.getContext());
+    public void clearExecutorQueue() {
+        executor.shutdownNow();
+        if (AppConfig.DEBUG)
+            Log.d(TAG, "Executor was shut down.");
+        executor = createExecutor();
 
-		if (source != null && source.getImageLoaderCacheKey() != null) {
-            target.setTag(R.id.imageloader_key, source.getImageLoaderCacheKey());
-            CachedBitmap cBitmap = getBitmapFromThumbnailCache(source.getImageLoaderCacheKey());
-			if (cBitmap != null && cBitmap.getLength() >= length) {
-				target.setImageBitmap(cBitmap.getBitmap());
-			} else {
-				target.setImageResource(defaultCoverResource);
-				BitmapDecodeWorkerTask worker = new BitmapDecodeWorkerTask(
-						handler, target, source, length, IMAGE_TYPE_THUMBNAIL);
-				executor.submit(worker);
-			}
-		} else {
-			target.setImageResource(defaultCoverResource);
-		}
-	}
+    }
 
-	public void clearExecutorQueue() {
-		executor.shutdownNow();
-		if (AppConfig.DEBUG)
-			Log.d(TAG, "Executor was shut down.");
-		executor = createExecutor();
+    public void wipeImageCache() {
+        coverCache.evictAll();
+        thumbnailCache.evictAll();
+    }
 
-	}
+    public boolean isInThumbnailCache(String fileUrl) {
+        return thumbnailCache.get(fileUrl) != null;
+    }
 
-	public void wipeImageCache() {
-		coverCache.evictAll();
-		thumbnailCache.evictAll();
-	}
+    private CachedBitmap getBitmapFromThumbnailCache(String key) {
+        return thumbnailCache.get(key);
+    }
 
-	public boolean isInThumbnailCache(String fileUrl) {
-		return thumbnailCache.get(fileUrl) != null;
-	}
+    public void addBitmapToThumbnailCache(String key, CachedBitmap bitmap) {
+        thumbnailCache.put(key, bitmap);
+    }
 
-	private CachedBitmap getBitmapFromThumbnailCache(String key) {
-		return thumbnailCache.get(key);
-	}
+    public boolean isInCoverCache(String fileUrl) {
+        return coverCache.get(fileUrl) != null;
+    }
 
-	public void addBitmapToThumbnailCache(String key, CachedBitmap bitmap) {
-		thumbnailCache.put(key, bitmap);
-	}
+    private CachedBitmap getBitmapFromCoverCache(String key) {
+        return coverCache.get(key);
+    }
 
-	public boolean isInCoverCache(String fileUrl) {
-		return coverCache.get(fileUrl) != null;
-	}
+    public void addBitmapToCoverCache(String key, CachedBitmap bitmap) {
+        coverCache.put(key, bitmap);
+    }
 
-	private CachedBitmap getBitmapFromCoverCache(String key) {
-		return coverCache.get(key);
-	}
+    private int getDefaultCoverResource(Context context) {
+        return android.R.color.transparent;
+    }
 
-	public void addBitmapToCoverCache(String key, CachedBitmap bitmap) {
-		coverCache.put(key, bitmap);
-	}
+    /**
+     * Used by the BitmapDecodeWorker task to retrieve the source of the bitmap.
+     */
+    public interface ImageWorkerTaskResource {
+        /**
+         * Opens a new InputStream that can be decoded as a bitmap by the
+         * BitmapFactory.
+         */
+        public InputStream openImageInputStream();
 
-	private int getDefaultCoverResource(Context context) {
-		return android.R.color.transparent;
-	}
+        /**
+         * Returns an InputStream that points to the beginning of the image
+         * resource. Implementations can either create a new InputStream or
+         * reset the existing one, depending on their implementation of
+         * openInputStream. If a new InputStream is returned, the one given as a
+         * parameter MUST be closed.
+         *
+         * @param input The input stream that was returned by openImageInputStream()
+         */
+        public InputStream reopenImageInputStream(InputStream input);
 
-	/**
-	 * Used by the BitmapDecodeWorker task to retrieve the source of the bitmap.
-	 */
-	public interface ImageWorkerTaskResource {
-		/**
-		 * Opens a new InputStream that can be decoded as a bitmap by the
-		 * BitmapFactory.
-		 */
-		public InputStream openImageInputStream();
-
-		/**
-		 * Returns an InputStream that points to the beginning of the image
-		 * resource. Implementations can either create a new InputStream or
-		 * reset the existing one, depending on their implementation of
-		 * openInputStream. If a new InputStream is returned, the one given as a
-		 * parameter MUST be closed.
-		 * @param input The input stream that was returned by openImageInputStream()
-		 * */
-		public InputStream reopenImageInputStream(InputStream input);
-
-		/**
-		 * Returns a string that identifies the image resource. Example: file
-		 * path of an image
-		 */
-		public String getImageLoaderCacheKey();
-	}
+        /**
+         * Returns a string that identifies the image resource. Example: file
+         * path of an image
+         */
+        public String getImageLoaderCacheKey();
+    }
 
 }