2 * Copyright (C) 2012 Andrew Neal
3 * Copyright (C) 2014 The CyanogenMod Project
4 * Licensed under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with the
6 * License. You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
8 * or agreed to in writing, software distributed under the License is
9 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
10 * KIND, either express or implied. See the License for the specific language
11 * governing permissions and limitations under the License.
14 package com.cyanogenmod.eleven.cache;
16 import android.content.ContentResolver;
17 import android.content.Context;
18 import android.graphics.Bitmap;
19 import android.graphics.BitmapFactory;
20 import android.net.Uri;
21 import android.widget.ImageView;
22 import com.cyanogenmod.eleven.Config;
23 import com.cyanogenmod.eleven.MusicPlaybackService;
24 import com.cyanogenmod.eleven.cache.PlaylistWorkerTask.PlaylistWorkerType;
25 import com.cyanogenmod.eleven.utils.MusicUtils;
26 import com.cyanogenmod.eleven.widgets.BlurScrimImage;
27 import com.cyanogenmod.eleven.widgets.LetterTileDrawable;
29 import java.io.FileNotFoundException;
30 import java.io.IOException;
31 import java.io.InputStream;
34 * A subclass of {@link ImageWorker} that fetches images from a URL.
36 public class ImageFetcher extends ImageWorker {
38 private static final int DEFAULT_MAX_IMAGE_HEIGHT = 1024;
40 private static final int DEFAULT_MAX_IMAGE_WIDTH = 1024;
42 private static ImageFetcher sInstance = null;
45 * Creates a new instance of {@link ImageFetcher}.
47 * @param context The {@link Context} to use.
49 public ImageFetcher(final Context context) {
54 * Used to create a singleton of the image fetcher
56 * @param context The {@link Context} to use
57 * @return A new instance of this class.
59 public static final ImageFetcher getInstance(final Context context) {
60 if (sInstance == null) {
61 sInstance = new ImageFetcher(context.getApplicationContext());
67 * Loads a playlist's most played song's artist image
68 * @param playlistId id of the playlist
69 * @param imageView imageview to load into
71 public void loadPlaylistArtistImage(final long playlistId, final ImageView imageView) {
72 loadPlaylistImage(playlistId, PlaylistWorkerType.Artist, imageView);
76 * Loads a playlist's most played songs into a combined image, or show 1 if not enough images
77 * @param playlistId id of the playlist
78 * @param imageView imageview to load into
80 public void loadPlaylistCoverArtImage(final long playlistId, final ImageView imageView) {
81 loadPlaylistImage(playlistId, PlaylistWorkerType.CoverArt, imageView);
85 * Used to fetch album images.
87 public void loadAlbumImage(final String artistName, final String albumName, final long albumId,
88 final ImageView imageView) {
89 loadImage(generateAlbumCacheKey(albumName, artistName), artistName, albumName, albumId, imageView,
94 * Used to fetch the current artwork.
96 public void loadCurrentArtwork(final ImageView imageView) {
97 loadImage(getCurrentCacheKey(),
98 MusicUtils.getArtistName(), MusicUtils.getAlbumName(), MusicUtils.getCurrentAlbumId(),
99 imageView, ImageType.ALBUM);
103 * Used to fetch the current artwork blurred.
105 public void loadCurrentBlurredArtwork(final BlurScrimImage image) {
106 loadBlurImage(getCurrentCacheKey(),
107 MusicUtils.getArtistName(), MusicUtils.getAlbumName(), MusicUtils.getCurrentAlbumId(),
108 image, ImageType.ALBUM);
111 public static String getCurrentCacheKey() {
112 return generateAlbumCacheKey(MusicUtils.getAlbumName(), MusicUtils.getArtistName());
116 * Used to fetch artist images.
118 public void loadArtistImage(final String key, final ImageView imageView) {
119 loadImage(key, key, null, -1, imageView, ImageType.ARTIST);
123 * Used to fetch artist images. It also scales the image to fit the image view, if necessary.
125 public void loadArtistImage(final String key, final ImageView imageView, boolean scaleImgToView) {
126 loadImage(key, key, null, -1, imageView, ImageType.ARTIST, scaleImgToView);
130 * Used to fetch the current artist image.
132 public void loadCurrentArtistImage(final ImageView imageView) {
133 loadImage(MusicUtils.getArtistName(), MusicUtils.getArtistName(), null, -1, imageView,
138 * @param pause True to temporarily pause the disk cache, false otherwise.
140 public void setPauseDiskCache(final boolean pause) {
141 if (mImageCache != null) {
142 mImageCache.setPauseDiskCache(pause);
147 * Clears the disk and memory caches
149 public void clearCaches() {
150 if (mImageCache != null) {
151 mImageCache.clearCaches();
154 // clear the keys of images we've already downloaded
158 public void addCacheListener(ICacheListener listener) {
159 if (mImageCache != null) {
160 mImageCache.addCacheListener(listener);
164 public void removeCacheListener(ICacheListener listener) {
165 if (mImageCache != null) {
166 mImageCache.removeCacheListener(listener);
171 * @param key The key used to find the image to remove
173 public void removeFromCache(final String key) {
174 if (mImageCache != null) {
175 mImageCache.removeFromCache(key);
180 * Finds cached or downloads album art. Used in {@link MusicPlaybackService}
181 * to set the current album art in the notification and lock screen
183 * @param albumName The name of the current album
184 * @param albumId The ID of the current album
185 * @param artistName The album artist in case we should have to download
187 * @param smallArtwork Get the small version of the default artwork if no artwork exists
188 * @return The album art as an {@link Bitmap}
190 public Bitmap getArtwork(final String albumName, final long albumId, final String artistName,
191 boolean smallArtwork) {
192 // Check the disk cache
193 Bitmap artwork = null;
194 String key = String.valueOf(albumId);
196 if (artwork == null && albumName != null && mImageCache != null) {
197 artwork = mImageCache.getBitmapFromDiskCache(key);
199 if (artwork == null && albumId >= 0 && mImageCache != null) {
200 // Check for local artwork
201 artwork = mImageCache.getArtworkFromFile(mContext, albumId);
203 if (artwork != null) {
207 return LetterTileDrawable.createDefaultBitmap(mContext, key, ImageType.ALBUM, false,
212 * Generates key used by album art cache. It needs both album name and artist name
213 * to let to select correct image for the case when there are two albums with the
216 * @param albumName The album name the cache key needs to be generated.
217 * @param artistName The artist name the cache key needs to be generated.
220 public static String generateAlbumCacheKey(final String albumName, final String artistName) {
221 if (albumName == null || artistName == null) {
224 return new StringBuilder(albumName)
228 .append(Config.ALBUM_ART_SUFFIX)
233 * Decode and sample down a {@link Bitmap} from a Uri.
235 * @param selectedImage Uri of the Image to decode
236 * @return A {@link Bitmap} sampled down from the original with the same
237 * aspect ratio and dimensions that are equal to or greater than the
238 * requested width and height
240 public static Bitmap decodeSampledBitmapFromUri(ContentResolver cr, final Uri selectedImage) {
241 // First decode with inJustDecodeBounds=true to check dimensions
242 final BitmapFactory.Options options = new BitmapFactory.Options();
243 options.inJustDecodeBounds = true;
246 InputStream input = cr.openInputStream(selectedImage);
247 BitmapFactory.decodeStream(input, null, options);
250 if (options.outHeight == -1 || options.outWidth == -1) {
254 // Calculate inSampleSize
255 options.inSampleSize = calculateInSampleSize(options, DEFAULT_MAX_IMAGE_WIDTH,
256 DEFAULT_MAX_IMAGE_HEIGHT);
258 // Decode bitmap with inSampleSize set
259 options.inJustDecodeBounds = false;
260 input = cr.openInputStream(selectedImage);
261 return BitmapFactory.decodeStream(input, null, options);
262 } catch (FileNotFoundException e) {
265 } catch (IOException e) {
273 * Calculate an inSampleSize for use in a
274 * {@link android.graphics.BitmapFactory.Options} object when decoding
275 * bitmaps using the decode* methods from {@link BitmapFactory}. This
276 * implementation calculates the closest inSampleSize that will result in
277 * the final decoded bitmap having a width and height equal to or larger
278 * than the requested width and height. This implementation does not ensure
279 * a power of 2 is returned for inSampleSize which can be faster when
280 * decoding but results in a larger bitmap which isn't as useful for caching
283 * @param options An options object with out* params already populated (run
284 * through a decode* method with inJustDecodeBounds==true
285 * @param reqWidth The requested width of the resulting bitmap
286 * @param reqHeight The requested height of the resulting bitmap
287 * @return The value to be used for inSampleSize
289 public static final int calculateInSampleSize(final BitmapFactory.Options options,
290 final int reqWidth, final int reqHeight) {
291 /* Raw height and width of image */
292 final int height = options.outHeight;
293 final int width = options.outWidth;
294 int inSampleSize = 1;
296 if (height > reqHeight || width > reqWidth) {
297 if (width > height) {
298 inSampleSize = Math.round((float)height / (float)reqHeight);
300 inSampleSize = Math.round((float)width / (float)reqWidth);
303 // This offers some additional logic in case the image has a strange
304 // aspect ratio. For example, a panorama may have a much larger
305 // width than height. In these cases the total pixels might still
306 // end up being too large to fit comfortably in memory, so we should
307 // be more aggressive with sample down the image (=larger
310 final float totalPixels = width * height;
312 /* More than 2x the requested pixels we'll sample down further */
313 final float totalReqPixelsCap = reqWidth * reqHeight * 2;
315 while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {