2 * Copyright (C) 2012 Andrew Neal Licensed under the Apache License, Version 2.0
3 * (the "License"); you may not use this file except in compliance with the
4 * License. You may obtain a copy of the License at
5 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
6 * or agreed to in writing, software distributed under the License is
7 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
8 * KIND, either express or implied. See the License for the specific language
9 * governing permissions and limitations under the License.
12 package com.cyngn.eleven.cache;
14 import android.content.Context;
15 import android.content.res.Resources;
16 import android.graphics.Bitmap;
17 import android.graphics.Color;
18 import android.graphics.drawable.BitmapDrawable;
19 import android.graphics.drawable.ColorDrawable;
20 import android.graphics.drawable.Drawable;
21 import android.graphics.drawable.TransitionDrawable;
22 import android.os.AsyncTask;
23 import android.widget.ImageView;
25 import com.cyngn.eleven.R;
26 import com.cyngn.eleven.utils.ApolloUtils;
27 import com.cyngn.eleven.utils.ThemeUtils;
29 import java.lang.ref.WeakReference;
30 import java.util.concurrent.RejectedExecutionException;
33 * This class wraps up completing some arbitrary long running work when loading
34 * a {@link Bitmap} to an {@link ImageView}. It handles things like using a
35 * memory and disk cache, running the work in a background thread and setting a
38 public abstract class ImageWorker {
41 * Default transition drawable fade time
43 private static final int FADE_IN_TIME = 200;
48 private final BitmapDrawable mDefaultArtwork;
51 * The resources to use
53 private final Resources mResources;
56 * First layer of the transition drawable
58 private final ColorDrawable mCurrentDrawable;
61 * Layer drawable used to cross fade the result from the worker
63 private final Drawable[] mArrayDrawable;
68 private final Bitmap mDefault;
73 protected Context mContext;
76 * Disk and memory caches
78 protected ImageCache mImageCache;
81 * Constructor of <code>ImageWorker</code>
83 * @param context The {@link Context} to use
85 protected ImageWorker(final Context context) {
86 mContext = context.getApplicationContext();
87 mResources = mContext.getResources();
88 // Create the default artwork
89 final ThemeUtils theme = new ThemeUtils(context);
90 mDefault = ((BitmapDrawable) theme.getDrawable("default_artwork")).getBitmap();
91 mDefaultArtwork = new BitmapDrawable(mResources, mDefault);
92 // No filter and no dither makes things much quicker
93 mDefaultArtwork.setFilterBitmap(false);
94 mDefaultArtwork.setDither(false);
95 // Create the transparent layer for the transition drawable
96 mCurrentDrawable = new ColorDrawable(mResources.getColor(R.color.transparent));
97 // A transparent image (layer 0) and the new result (layer 1)
98 mArrayDrawable = new Drawable[2];
99 mArrayDrawable[0] = mCurrentDrawable;
100 // XXX The second layer is set in the worker task.
104 * Set the {@link ImageCache} object to use with this ImageWorker.
106 * @param cacheCallback new {@link ImageCache} object.
108 public void setImageCache(final ImageCache cacheCallback) {
109 mImageCache = cacheCallback;
113 * Closes the disk cache associated with this ImageCache object. Note that
114 * this includes disk access so this should not be executed on the main/UI
117 public void close() {
118 if (mImageCache != null) {
124 * flush() is called to synchronize up other methods that are accessing the
127 public void flush() {
128 if (mImageCache != null) {
134 * Adds a new image to the memory and disk caches
136 * @param data The key used to store the image
137 * @param bitmap The {@link Bitmap} to cache
139 public void addBitmapToCache(final String key, final Bitmap bitmap) {
140 if (mImageCache != null) {
141 mImageCache.addBitmapToCache(key, bitmap);
146 * @return The deafult artwork
148 public Bitmap getDefaultArtwork() {
153 * The actual {@link AsyncTask} that will process the image.
155 private final class BitmapWorkerTask extends AsyncTask<String, Void, TransitionDrawable> {
158 * The {@link ImageView} used to set the result
160 private final WeakReference<ImageView> mImageReference;
163 * Type of URL to download
165 private final ImageType mImageType;
168 * The key used to store cached entries
175 private String mArtistName;
180 private String mAlbumName;
183 * The album ID used to find the corresponding artwork
185 private long mAlbumId;
188 * The URL of an image to download
193 * Constructor of <code>BitmapWorkerTask</code>
195 * @param imageView The {@link ImageView} to use.
196 * @param imageType The type of image URL to fetch for.
198 @SuppressWarnings("deprecation")
199 public BitmapWorkerTask(final ImageView imageView, final ImageType imageType) {
200 imageView.setBackgroundDrawable(mDefaultArtwork);
201 mImageReference = new WeakReference<ImageView>(imageView);
202 mImageType = imageType;
209 protected TransitionDrawable doInBackground(final String... params) {
214 Bitmap bitmap = null;
216 // First, check the disk cache for the image
217 if (mKey != null && mImageCache != null && !isCancelled()
218 && getAttachedImageView() != null) {
219 bitmap = mImageCache.getCachedBitmap(mKey);
222 // Define the album id now
223 mAlbumId = Long.valueOf(params[3]);
225 // Second, if we're fetching artwork, check the device for the image
226 if (bitmap == null && mImageType.equals(ImageType.ALBUM) && mAlbumId >= 0
227 && mKey != null && !isCancelled() && getAttachedImageView() != null
228 && mImageCache != null) {
229 bitmap = mImageCache.getCachedArtwork(mContext, mKey, mAlbumId);
232 // Third, by now we need to download the image
233 if (bitmap == null && ApolloUtils.isOnline(mContext) && !isCancelled()
234 && getAttachedImageView() != null) {
235 // Now define what the artist name, album name, and url are.
236 mArtistName = params[1];
237 mAlbumName = params[2];
238 mUrl = processImageUrl(mArtistName, mAlbumName, mImageType);
240 bitmap = processBitmap(mUrl);
244 // Fourth, add the new image to the cache
245 if (bitmap != null && mKey != null && mImageCache != null) {
246 addBitmapToCache(mKey, bitmap);
249 // Add the second layer to the transiation drawable
250 if (bitmap != null) {
251 final BitmapDrawable layerTwo = new BitmapDrawable(mResources, bitmap);
252 layerTwo.setFilterBitmap(false);
253 layerTwo.setDither(false);
254 mArrayDrawable[1] = layerTwo;
256 // Finally, return the image
257 final TransitionDrawable result = new TransitionDrawable(mArrayDrawable);
258 result.setCrossFadeEnabled(true);
259 result.startTransition(FADE_IN_TIME);
269 protected void onPostExecute(TransitionDrawable result) {
273 final ImageView imageView = getAttachedImageView();
274 if (result != null && imageView != null) {
275 imageView.setImageDrawable(result);
280 * @return The {@link ImageView} associated with this task as long as
281 * the ImageView's task still points to this task as well.
282 * Returns null otherwise.
284 private final ImageView getAttachedImageView() {
285 final ImageView imageView = mImageReference.get();
286 final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
287 if (this == bitmapWorkerTask) {
295 * Calls {@code cancel()} in the worker task
297 * @param imageView the {@link ImageView} to use
299 public static final void cancelWork(final ImageView imageView) {
300 final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
301 if (bitmapWorkerTask != null) {
302 bitmapWorkerTask.cancel(true);
307 * Returns true if the current work has been canceled or if there was no
308 * work in progress on this image view. Returns false if the work in
309 * progress deals with the same data. The work is not stopped in that case.
311 public static final boolean executePotentialWork(final Object data, final ImageView imageView) {
312 final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
313 if (bitmapWorkerTask != null) {
314 final Object bitmapData = bitmapWorkerTask.mKey;
315 if (bitmapData == null || !bitmapData.equals(data)) {
316 bitmapWorkerTask.cancel(true);
318 // The same work is already in progress
326 * Used to determine if the current image drawable has an instance of
327 * {@link BitmapWorkerTask}
329 * @param imageView Any {@link ImageView}.
330 * @return Retrieve the currently active work task (if any) associated with
331 * this {@link ImageView}. null if there is no such task.
333 private static final BitmapWorkerTask getBitmapWorkerTask(final ImageView imageView) {
334 if (imageView != null) {
335 final Drawable drawable = imageView.getDrawable();
336 if (drawable instanceof AsyncDrawable) {
337 final AsyncDrawable asyncDrawable = (AsyncDrawable)drawable;
338 return asyncDrawable.getBitmapWorkerTask();
345 * A custom {@link BitmapDrawable} that will be attached to the
346 * {@link ImageView} while the work is in progress. Contains a reference to
347 * the actual worker task, so that it can be stopped if a new binding is
348 * required, and makes sure that only the last started worker process can
349 * bind its result, independently of the finish order.
351 private static final class AsyncDrawable extends ColorDrawable {
353 private final WeakReference<BitmapWorkerTask> mBitmapWorkerTaskReference;
356 * Constructor of <code>AsyncDrawable</code>
358 public AsyncDrawable(final Resources res, final Bitmap bitmap,
359 final BitmapWorkerTask mBitmapWorkerTask) {
360 super(Color.TRANSPARENT);
361 mBitmapWorkerTaskReference = new WeakReference<BitmapWorkerTask>(mBitmapWorkerTask);
365 * @return The {@link BitmapWorkerTask} associated with this drawable
367 public BitmapWorkerTask getBitmapWorkerTask() {
368 return mBitmapWorkerTaskReference.get();
373 * Called to fetch the artist or ablum art.
375 * @param key The unique identifier for the image.
376 * @param artistName The artist name for the Last.fm API.
377 * @param albumName The album name for the Last.fm API.
378 * @param albumId The album art index, to check for missing artwork.
379 * @param imageView The {@link ImageView} used to set the cached
381 * @param imageType The type of image URL to fetch for.
383 protected void loadImage(final String key, final String artistName, final String albumName,
384 final long albumId, final ImageView imageView, final ImageType imageType) {
385 if (key == null || mImageCache == null || imageView == null) {
388 // First, check the memory for the image
389 final Bitmap lruBitmap = mImageCache.getBitmapFromMemCache(key);
390 if (lruBitmap != null && imageView != null) {
391 // Bitmap found in memory cache
392 imageView.setImageBitmap(lruBitmap);
393 } else if (executePotentialWork(key, imageView)
394 && imageView != null && !mImageCache.isDiskCachePaused()) {
395 // Otherwise run the worker task
396 final BitmapWorkerTask bitmapWorkerTask = new BitmapWorkerTask(imageView, imageType);
397 final AsyncDrawable asyncDrawable = new AsyncDrawable(mResources, mDefault,
399 imageView.setImageDrawable(asyncDrawable);
401 ApolloUtils.execute(false, bitmapWorkerTask, key,
402 artistName, albumName, String.valueOf(albumId));
403 } catch (RejectedExecutionException e) {
404 // Executor has exhausted queue space, show default artwork
405 imageView.setImageBitmap(getDefaultArtwork());
411 * Subclasses should override this to define any processing or work that
412 * must happen to produce the final {@link Bitmap}. This will be executed in
413 * a background thread and be long running.
415 * @param key The key to identify which image to process, as provided by
416 * {@link ImageWorker#loadImage(mKey, ImageView)}
417 * @return The processed {@link Bitmap}.
419 protected abstract Bitmap processBitmap(String key);
422 * Subclasses should override this to define any processing or work that
423 * must happen to produce the URL needed to fetch the final {@link Bitmap}.
425 * @param artistName The artist name param used in the Last.fm API.
426 * @param albumName The album name param used in the Last.fm API.
427 * @param imageType The type of image URL to fetch for.
428 * @return The image URL for an artist image or album image.
430 protected abstract String processImageUrl(String artistName, String albumName,
431 ImageType imageType);
434 * Used to define what type of image URL to fetch for, artist or album.
436 public enum ImageType {