diff options
Diffstat (limited to 'src/com/cyngn/eleven/cache/ImageWorker.java')
-rw-r--r-- | src/com/cyngn/eleven/cache/ImageWorker.java | 563 |
1 files changed, 129 insertions, 434 deletions
diff --git a/src/com/cyngn/eleven/cache/ImageWorker.java b/src/com/cyngn/eleven/cache/ImageWorker.java index aa78a79..b31b2f1 100644 --- a/src/com/cyngn/eleven/cache/ImageWorker.java +++ b/src/com/cyngn/eleven/cache/ImageWorker.java @@ -19,18 +19,16 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.TransitionDrawable; -import android.os.AsyncTask; -import android.support.v7.graphics.Palette; -import android.support.v7.graphics.PaletteItem; -import android.support.v8.renderscript.Allocation; -import android.support.v8.renderscript.Element; import android.support.v8.renderscript.RenderScript; -import android.support.v8.renderscript.ScriptIntrinsicBlur; +import android.view.View; import android.widget.ImageView; import com.cyngn.eleven.R; +import com.cyngn.eleven.provider.PlaylistArtworkStore; import com.cyngn.eleven.utils.ApolloUtils; +import com.cyngn.eleven.utils.ImageUtils; import com.cyngn.eleven.widgets.BlurScrimImage; +import com.cyngn.eleven.cache.PlaylistWorkerTask.PlaylistWorkerType; import java.lang.ref.WeakReference; import java.util.concurrent.RejectedExecutionException; @@ -46,7 +44,7 @@ public abstract class ImageWorker { /** * Render script */ - private static RenderScript sRenderScript = null; + public static RenderScript sRenderScript = null; /** * Default transition drawable fade time @@ -183,6 +181,10 @@ public abstract class ImageWorker { targetBitmap = mDefaultArtist; break; + case PLAYLIST: + targetBitmap = mDefaultPlaylist; + break; + case ALBUM: default: targetBitmap = mDefault; @@ -197,344 +199,38 @@ public abstract class ImageWorker { return bitmapDrawable; } - /** - * The actual {@link AsyncTask} that will process the image. - */ - private class BitmapWorkerTask extends AsyncTask<String, Void, Object> { - - /** - * The {@link ImageView} used to set the result - */ - private final WeakReference<ImageView> mImageReference; - - /** - * Type of URL to download - */ - private final ImageType mImageType; - - /** - * The key used to store cached entries - */ - private String mKey; - - /** - * Artist name param - */ - private String mArtistName; - - /** - * Album name parm - */ - private String mAlbumName; - - /** - * The album ID used to find the corresponding artwork - */ - private long mAlbumId; - - /** - * The URL of an image to download - */ - private String mUrl; - - /** - * Layer drawable used to cross fade the result from the worker - */ - protected Drawable mFromDrawable; - - /** - * Constructor of <code>BitmapWorkerTask</code> - * - * @param imageView The {@link ImageView} to use. - * @param imageType The type of image URL to fetch for. - */ - @SuppressWarnings("deprecation") - public BitmapWorkerTask(final ImageView imageView, final ImageType imageType) { - mImageReference = new WeakReference<ImageView>(imageView); - mImageType = imageType; - - // A transparent image (layer 0) and the new result (layer 1) - mFromDrawable = mTransparentDrawable; - } - - protected Bitmap getBitmapInBackground(final String... params) { - // Define the key - mKey = params[0]; - - // The result - Bitmap bitmap = null; - - // First, check the disk cache for the image - if (mKey != null && mImageCache != null && !isCancelled() - && getAttachedImageView() != null) { - bitmap = mImageCache.getCachedBitmap(mKey); - } - - // Define the album id now - mAlbumId = Long.valueOf(params[3]); - - // Second, if we're fetching artwork, check the device for the image - if (bitmap == null && mImageType.equals(ImageType.ALBUM) && mAlbumId >= 0 - && mKey != null && !isCancelled() && getAttachedImageView() != null - && mImageCache != null) { - bitmap = mImageCache.getCachedArtwork(mContext, mKey, mAlbumId); - } - - // Third, by now we need to download the image - if (bitmap == null && ApolloUtils.isOnline(mContext) && !isCancelled() - && getAttachedImageView() != null) { - // Now define what the artist name, album name, and url are. - mArtistName = params[1]; - mAlbumName = params[2]; - mUrl = processImageUrl(mArtistName, mAlbumName, mImageType); - if (mUrl != null) { - bitmap = processBitmap(mUrl); - } - } - - // Fourth, add the new image to the cache - if (bitmap != null && mKey != null && mImageCache != null) { - addBitmapToCache(mKey, bitmap); - } - - return bitmap; - } - - /** - * {@inheritDoc} - */ - @Override - protected Object doInBackground(final String... params) { - final Bitmap bitmap = getBitmapInBackground(params); - return createImageTransitionDrawable(mResources, mFromDrawable, bitmap, - FADE_IN_TIME, false, false); - } - - /** - * {@inheritDoc} - */ - @Override - protected void onPostExecute(Object result) { - if (isCancelled()) { - return; - } - - TransitionDrawable transitionDrawable = (TransitionDrawable)result; - - final ImageView imageView = getAttachedImageView(); - if (transitionDrawable != null && imageView != null) { - imageView.setImageDrawable(transitionDrawable); - } - } - - /** - * @return The {@link ImageView} associated with this task as long as - * the ImageView's task still points to this task as well. - * Returns null otherwise. - */ - protected ImageView getAttachedImageView() { - final ImageView imageView = mImageReference.get(); - final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); - if (this == bitmapWorkerTask) { - return imageView; - } - return null; - } - } - - /** - * This will download the image (if needed) and create a blur and set the scrim as well on the - * BlurScrimImage - */ - private class BlurBitmapWorkerTask extends BitmapWorkerTask { - // if the image is too small, the blur will look bad post scale up so we use the min size - // to scale up before bluring - private static final int MIN_BITMAP_SIZE = 500; - // number of times to run the blur - private static final int NUM_BLUR_RUNS = 8; - // 25f is the max blur radius possible - private static final float BLUR_RADIUS = 25f; - - // container for the result - private class ResultContainer { - public TransitionDrawable mImageViewBitmapDrawable; - public int mPaletteColor; - } - - /** - * The {@link BlurScrimImage} used to set the result - */ - private final WeakReference<BlurScrimImage> mBlurScrimImage; + public static Bitmap getBitmapInBackground(final Context context, final ImageCache imageCache, + final String key, final String albumName, final String artistName, + final long albumId, final ImageType imageType) { + // The result + Bitmap bitmap = null; - /** - * Constructor of <code>BitmapWorkerTask</code> - * - * @param blurScrimImage The {@link BlurScrimImage} to use. - * @param imageType The type of image URL to fetch for. - */ - @SuppressWarnings("deprecation") - public BlurBitmapWorkerTask(final BlurScrimImage blurScrimImage, final ImageType imageType) { - super(blurScrimImage.getImageView(), imageType); - mBlurScrimImage = new WeakReference<BlurScrimImage>(blurScrimImage); - - // use the existing image as the drawable and if it doesn't exist fallback to transparent - mFromDrawable = blurScrimImage.getImageView().getDrawable(); - if (mFromDrawable == null) { - mFromDrawable = mTransparentDrawable; - } + // First, check the disk cache for the image + if (key != null && imageCache != null) { + bitmap = imageCache.getCachedBitmap(key); } - /** - * {@inheritDoc} - */ - @Override - protected Object doInBackground(final String... params) { - Bitmap bitmap = getBitmapInBackground(params); - - ResultContainer result = new ResultContainer(); - - Bitmap output = null; - - if (bitmap != null) { - // now create the blur bitmap - Bitmap input = bitmap; - - // if the image is too small, scale it up before running through the blur - if (input.getWidth() < MIN_BITMAP_SIZE || input.getHeight() < MIN_BITMAP_SIZE) { - float multiplier = Math.max(MIN_BITMAP_SIZE / (float)input.getWidth(), - MIN_BITMAP_SIZE / (float)input.getHeight()); - input = input.createScaledBitmap(bitmap, (int)(input.getWidth() * multiplier), - (int)(input.getHeight() * multiplier), true); - // since we created a new bitmap, we can re-use the bitmap for our output - output = input; - } else { - // if we aren't creating a new bitmap, create a new output bitmap - output = Bitmap.createBitmap(input.getWidth(), input.getHeight(), input.getConfig()); - } - - // run the blur multiple times - for (int i = 0; i < NUM_BLUR_RUNS; i++) { - final Allocation inputAlloc = Allocation.createFromBitmap(sRenderScript, input); - final Allocation outputAlloc = Allocation.createTyped(sRenderScript, - inputAlloc.getType()); - final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(sRenderScript, - Element.U8_4(sRenderScript)); - - script.setRadius(BLUR_RADIUS); - script.setInput(inputAlloc); - script.forEach(outputAlloc); - outputAlloc.copyTo(output); - - // if we run more than 1 blur, the new input should be the old output - input = output; - } - - // calculate the palette color - result.mPaletteColor = getPaletteColorInBackground(output); - - // create the bitmap transition drawable - result.mImageViewBitmapDrawable = createImageTransitionDrawable(mResources, mFromDrawable, - output, FADE_IN_TIME_SLOW, true, true); - - return result; - } - - return null; + // Second, if we're fetching artwork, check the device for the image + if (bitmap == null && imageType.equals(ImageType.ALBUM) && albumId >= 0 + && key != null && imageCache != null) { + bitmap = imageCache.getCachedArtwork(context, key, albumId); } - /** - * This will get the most vibrant palette color for a bitmap - * @param input to process - * @return the most vibrant color or transparent if none found - */ - private int getPaletteColorInBackground(Bitmap input) { - int color = Color.TRANSPARENT; - - if (input != null) { - Palette palette = Palette.generate(input); - PaletteItem paletteItem = palette.getVibrantColor(); - - // keep walking through the palette items to find a color if we don't have any - if (paletteItem == null) { - paletteItem = palette.getVibrantColor(); - } - - if (paletteItem == null) { - paletteItem = palette.getLightVibrantColor(); - } - - if (paletteItem == null) { - paletteItem = palette.getLightMutedColor(); - } - - if (paletteItem == null) { - paletteItem = palette.getLightMutedColor(); - } - - if (paletteItem == null) { - paletteItem = palette.getDarkVibrantColor(); - } - - if (paletteItem == null) { - paletteItem = palette.getMutedColor(); - } - - if (paletteItem == null) { - paletteItem = palette.getDarkMutedColor(); - } - - if (paletteItem != null) { - // grab the rgb values - color = paletteItem.getRgb() | 0xFFFFFF; - - // make it 20% opacity - color &= 0x33000000; - } + // Third, by now we need to download the image + if (bitmap == null && ApolloUtils.isOnline(context)) { + // Now define what the artist name, album name, and url are. + String url = ImageUtils.processImageUrl(context, artistName, albumName, imageType); + if (url != null) { + bitmap = ImageUtils.processBitmap(context, url); } - - return color; } - /** - * {@inheritDoc} - */ - @Override - protected void onPostExecute(Object result) { - if (isCancelled()) { - return; - } - - BlurScrimImage blurScrimImage = mBlurScrimImage.get(); - if (blurScrimImage != null) { - if (result == null) { - // if we have no image, then signal the transition to the default state - blurScrimImage.transitionToDefaultState(); - } else { - ResultContainer resultContainer = (ResultContainer)result; - - // create the palette transition - TransitionDrawable paletteTransition = createPaletteTransition(blurScrimImage, - resultContainer.mPaletteColor); - - // set the transition drawable - blurScrimImage.setTransitionDrawable(false, - resultContainer.mImageViewBitmapDrawable, paletteTransition); - } - } + // Fourth, add the new image to the cache + if (bitmap != null && key != null && imageCache != null) { + imageCache.addBitmapToCache(key, bitmap); } - /** - * {@inheritDoc} - */ - @Override - protected final ImageView getAttachedImageView() { - final BlurScrimImage blurImage = mBlurScrimImage.get(); - final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(blurImage); - if (this == bitmapWorkerTask) { - return blurImage.getImageView(); - } - return null; - } + return bitmap; } /** @@ -601,25 +297,21 @@ public abstract class ImageWorker { } /** - * Calls {@code cancel()} in the worker task - * - * @param imageView the {@link ImageView} to use + * Cancels and clears out any pending bitmap worker tasks on this image view + * @param image ImageView to check */ - public static final void cancelWork(final ImageView imageView) { - final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); - if (bitmapWorkerTask != null) { - bitmapWorkerTask.cancel(true); - } - } + public static final void cancelWork(final ImageView image) { + Object tag = image.getTag(); + if (tag != null && tag instanceof AsyncTaskContainer) { + AsyncTaskContainer asyncTaskContainer = (AsyncTaskContainer)tag; + BitmapWorkerTask bitmapWorkerTask = asyncTaskContainer.getBitmapWorkerTask(); + if (bitmapWorkerTask != null) { + bitmapWorkerTask.cancel(false); + } - /** - * Returns true if the current work has been canceled or if there was no - * work in progress on this image view. Returns false if the work in - * progress deals with the same data. The work is not stopped in that case. - */ - public static final boolean executePotentialWork(final Object data, final ImageView imageView) { - final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); - return executePotentialWork(data, bitmapWorkerTask); + // clear out the tag + image.setTag(null); + } } /** @@ -627,8 +319,8 @@ public abstract class ImageWorker { * work in progress on this image view. Returns false if the work in * progress deals with the same data. The work is not stopped in that case. */ - public static final boolean executePotentialWork(final Object data, final BlurScrimImage image) { - final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(image); + public static final boolean executePotentialWork(final Object data, final View view) { + final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(view); return executePotentialWork(data, bitmapWorkerTask); } @@ -655,34 +347,16 @@ public abstract class ImageWorker { * Used to determine if the current image drawable has an instance of * {@link BitmapWorkerTask} * - * @param imageView Any {@link ImageView}. - * @return Retrieve the currently active work task (if any) associated with - * this {@link ImageView}. null if there is no such task. - */ - private static final BitmapWorkerTask getBitmapWorkerTask(final ImageView imageView) { - if (imageView != null) { - final Drawable drawable = imageView.getDrawable(); - if (drawable instanceof AsyncDrawable) { - final AsyncDrawable asyncDrawable = (AsyncDrawable)drawable; - return asyncDrawable.getBitmapWorkerTask(); - } - } - return null; - } - - /** - * Used to determine if the current image drawable has an instance of - * {@link BitmapWorkerTask} - * - * @param image Any {@link BlurScrimImage}. + * @param view Any {@link View} that either is or contains an ImageView. * @return Retrieve the currently active work task (if any) associated with - * this {@link BlurScrimImage}. null if there is no such task. - */ - private static final BitmapWorkerTask getBitmapWorkerTask(final BlurScrimImage image) { - if (image != null) { - final AsyncDrawable asyncDrawable = (AsyncDrawable)image.getTag(); - if (asyncDrawable != null) { - return asyncDrawable.getBitmapWorkerTask(); + * this {@link View}. null if there is no such task. + */ + public static final BitmapWorkerTask getBitmapWorkerTask(final View view) { + if (view != null) { + final Object tag = view.getTag(); + if (tag instanceof AsyncTaskContainer) { + final AsyncTaskContainer asyncTaskContainer = (AsyncTaskContainer)tag; + return asyncTaskContainer.getBitmapWorkerTask(); } } return null; @@ -690,21 +364,19 @@ public abstract class ImageWorker { /** * A custom {@link BitmapDrawable} that will be attached to the - * {@link ImageView} while the work is in progress. Contains a reference to - * the actual worker task, so that it can be stopped if a new binding is + * {@link View} which either is or contains an {@link ImageView} while the work is in progress. + * Contains a reference to the actual worker task, so that it can be stopped if a new binding is * required, and makes sure that only the last started worker process can * bind its result, independently of the finish order. */ - private static final class AsyncDrawable extends ColorDrawable { + public static final class AsyncTaskContainer { private final WeakReference<BitmapWorkerTask> mBitmapWorkerTaskReference; /** * Constructor of <code>AsyncDrawable</code> */ - public AsyncDrawable(final Resources res, final Bitmap bitmap, - final BitmapWorkerTask mBitmapWorkerTask) { - super(Color.TRANSPARENT); + public AsyncTaskContainer(final BitmapWorkerTask mBitmapWorkerTask) { mBitmapWorkerTaskReference = new WeakReference<BitmapWorkerTask>(mBitmapWorkerTask); } @@ -717,7 +389,7 @@ public abstract class ImageWorker { } /** - * Called to fetch the artist or ablum art. + * Called to fetch the artist or album art. * * @param key The unique identifier for the image. * @param artistName The artist name for the Last.fm API. @@ -748,31 +420,79 @@ public abstract class ImageWorker { if (executePotentialWork(key, imageView) && imageView != null && !mImageCache.isDiskCachePaused()) { // cancel the old task if any - final Drawable previousDrawable = imageView.getDrawable(); - if (previousDrawable != null && previousDrawable instanceof AsyncDrawable) { - BitmapWorkerTask workerTask = ((AsyncDrawable)previousDrawable).getBitmapWorkerTask(); - if (workerTask != null) { - workerTask.cancel(false); - } - } + cancelWork(imageView); // Otherwise run the worker task - final BitmapWorkerTask bitmapWorkerTask = new BitmapWorkerTask(imageView, imageType); - final AsyncDrawable asyncDrawable = new AsyncDrawable(mResources, mDefault, - bitmapWorkerTask); - imageView.setImageDrawable(asyncDrawable); + final SimpleBitmapWorkerTask bitmapWorkerTask = new SimpleBitmapWorkerTask(key, + imageView, imageType, mTransparentDrawable, mContext); + final AsyncTaskContainer asyncTaskContainer = new AsyncTaskContainer(bitmapWorkerTask); + imageView.setTag(asyncTaskContainer); try { - ApolloUtils.execute(false, bitmapWorkerTask, key, + ApolloUtils.execute(false, bitmapWorkerTask, artistName, albumName, String.valueOf(albumId)); } catch (RejectedExecutionException e) { - // Executor has exhausted queue space, show default artwork - imageView.setImageBitmap(getDefaultArtwork()); + // Executor has exhausted queue space } } } } /** + * Called to fetch a playlist's top artist or cover art + * @param playlistId playlist identifier + * @param type of work to get (Artist or CoverArt) + * @param imageView to set the image to + */ + public void loadPlaylistImage(final long playlistId, final PlaylistWorkerType type, + final ImageView imageView) { + if (mImageCache == null || imageView == null) { + return; + } + + String key = null; + switch (type) { + case Artist: + key = PlaylistArtworkStore.getArtistCacheKey(playlistId); + break; + case CoverArt: + key = PlaylistArtworkStore.getCoverCacheKey(playlistId); + break; + } + + // First, check the memory for the image + final Bitmap lruBitmap = mImageCache.getBitmapFromMemCache(key); + if (lruBitmap != null) { + // Bitmap found in memory cache + imageView.setImageBitmap(lruBitmap); + } else { + // if a background drawable hasn't been set, create one so that even if + // the disk cache is paused we see something + if (imageView.getBackground() == null) { + imageView.setBackgroundDrawable(getNewDefaultBitmapDrawable(ImageType.PLAYLIST)); + } + } + + // even though we may have found the image in the cache, we want to check if the playlist + // has been updated, or it's been too long since the last update and change the image + // accordingly + if (executePotentialWork(key, imageView) && !mImageCache.isDiskCachePaused()) { + // cancel the old task if any + cancelWork(imageView); + + // Otherwise run the worker task + final PlaylistWorkerTask bitmapWorkerTask = new PlaylistWorkerTask(key, playlistId, type, + lruBitmap != null, imageView, mTransparentDrawable, mContext); + final AsyncTaskContainer asyncTaskContainer = new AsyncTaskContainer(bitmapWorkerTask); + imageView.setTag(asyncTaskContainer); + try { + ApolloUtils.execute(false, bitmapWorkerTask); + } catch (RejectedExecutionException e) { + // Executor has exhausted queue space + } + } + } + + /** * Called to fetch the blurred artist or album art. * * @param key The unique identifier for the image. @@ -795,7 +515,7 @@ public abstract class ImageWorker { if (executePotentialWork(blurKey, blurScrimImage) && blurScrimImage != null && !mImageCache.isDiskCachePaused()) { // cancel the old task if any - final AsyncDrawable previousDrawable = (AsyncDrawable)blurScrimImage.getTag(); + final AsyncTaskContainer previousDrawable = (AsyncTaskContainer)blurScrimImage.getTag(); if (previousDrawable != null) { BitmapWorkerTask workerTask = previousDrawable.getBitmapWorkerTask(); if (workerTask != null) { @@ -804,14 +524,13 @@ public abstract class ImageWorker { } // Otherwise run the worker task - final BlurBitmapWorkerTask blurWorkerTask = new BlurBitmapWorkerTask(blurScrimImage, imageType); - final AsyncDrawable asyncDrawable = new AsyncDrawable(mResources, mDefault, - blurWorkerTask); - blurScrimImage.setTag(asyncDrawable); + final BlurBitmapWorkerTask blurWorkerTask = new BlurBitmapWorkerTask(key, blurScrimImage, + imageType, mTransparentDrawable, mContext, sRenderScript); + final AsyncTaskContainer asyncTaskContainer = new AsyncTaskContainer(blurWorkerTask); + blurScrimImage.setTag(asyncTaskContainer); try { - ApolloUtils.execute(false, blurWorkerTask, key, - artistName, albumName, String.valueOf(albumId)); + ApolloUtils.execute(false, blurWorkerTask, artistName, albumName, String.valueOf(albumId)); } catch (RejectedExecutionException e) { // Executor has exhausted queue space, show default artwork blurScrimImage.transitionToDefaultState(); @@ -820,33 +539,9 @@ public abstract class ImageWorker { } /** - * Subclasses should override this to define any processing or work that - * must happen to produce the final {@link Bitmap}. This will be executed in - * a background thread and be long running. - * - * @param key The key to identify which image to process, as provided by - * {@link ImageWorker#loadImage(mKey, ImageView)} - * @return The processed {@link Bitmap}. - */ - protected abstract Bitmap processBitmap(String key); - - /** - * Subclasses should override this to define any processing or work that - * must happen to produce the URL needed to fetch the final {@link Bitmap}. - * - * @param artistName The artist name param used in the Last.fm API. - * @param albumName The album name param used in the Last.fm API. - * @param imageType The type of image URL to fetch for. - * @return The image URL for an artist image or album image. - */ - protected abstract String processImageUrl(String artistName, String albumName, - ImageType imageType); - - /** * Used to define what type of image URL to fetch for, artist or album. */ public enum ImageType { - ARTIST, ALBUM; + ARTIST, ALBUM, PLAYLIST; } - } |