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.BitmapWithColors;
26 import com.cyanogenmod.eleven.utils.MusicUtils;
27 import com.cyanogenmod.eleven.widgets.BlurScrimImage;
28 import com.cyanogenmod.eleven.widgets.LetterTileDrawable;
30 import java.io.FileNotFoundException;
31 import java.io.IOException;
32 import java.io.InputStream;
35 * A subclass of {@link ImageWorker} that fetches images from a URL.
37 public class ImageFetcher extends ImageWorker {
39 private static final int DEFAULT_MAX_IMAGE_HEIGHT = 1024;
41 private static final int DEFAULT_MAX_IMAGE_WIDTH = 1024;
43 private static ImageFetcher sInstance = null;
46 * Creates a new instance of {@link ImageFetcher}.
48 * @param context The {@link Context} to use.
50 public ImageFetcher(final Context context) {
55 * Used to create a singleton of the image fetcher
57 * @param context The {@link Context} to use
58 * @return A new instance of this class.
60 public static final ImageFetcher getInstance(final Context context) {
61 if (sInstance == null) {
62 sInstance = new ImageFetcher(context.getApplicationContext());
68 * Loads a playlist's most played song's artist image
69 * @param playlistId id of the playlist
70 * @param imageView imageview to load into
72 public void loadPlaylistArtistImage(final long playlistId, final ImageView imageView) {
73 loadPlaylistImage(playlistId, PlaylistWorkerType.Artist, imageView);
77 * Loads a playlist's most played songs into a combined image, or show 1 if not enough images
78 * @param playlistId id of the playlist
79 * @param imageView imageview to load into
81 public void loadPlaylistCoverArtImage(final long playlistId, final ImageView imageView) {
82 loadPlaylistImage(playlistId, PlaylistWorkerType.CoverArt, imageView);
86 * Used to fetch album images.
88 public void loadAlbumImage(final String artistName, final String albumName, final long albumId,
89 final ImageView imageView) {
90 loadImage(generateAlbumCacheKey(albumName, artistName), artistName, albumName, albumId, imageView,
95 * Used to fetch the current artwork.
97 public void loadCurrentArtwork(final ImageView imageView) {
98 loadImage(getCurrentCacheKey(),
99 MusicUtils.getArtistName(), MusicUtils.getAlbumName(), MusicUtils.getCurrentAlbumId(),
100 imageView, ImageType.ALBUM);
104 * Used to fetch the current artwork blurred.
106 public void loadCurrentBlurredArtwork(final BlurScrimImage image) {
107 loadBlurImage(getCurrentCacheKey(),
108 MusicUtils.getArtistName(), MusicUtils.getAlbumName(), MusicUtils.getCurrentAlbumId(),
109 image, ImageType.ALBUM);
112 public static String getCurrentCacheKey() {
113 return generateAlbumCacheKey(MusicUtils.getAlbumName(), MusicUtils.getArtistName());
117 * Used to fetch artist images.
119 public void loadArtistImage(final String key, final ImageView imageView) {
120 loadImage(key, key, null, -1, imageView, ImageType.ARTIST);
124 * Used to fetch artist images. It also scales the image to fit the image view, if necessary.
126 public void loadArtistImage(final String key, final ImageView imageView, boolean scaleImgToView) {
127 loadImage(key, key, null, -1, imageView, ImageType.ARTIST, scaleImgToView);
131 * Used to fetch the current artist image.
133 public void loadCurrentArtistImage(final ImageView imageView) {
134 loadImage(MusicUtils.getArtistName(), MusicUtils.getArtistName(), null, -1, imageView,
139 * @param pause True to temporarily pause the disk cache, false otherwise.
141 public void setPauseDiskCache(final boolean pause) {
142 if (mImageCache != null) {
143 mImageCache.setPauseDiskCache(pause);
148 * Clears the disk and memory caches
150 public void clearCaches() {
151 if (mImageCache != null) {
152 mImageCache.clearCaches();
155 // clear the keys of images we've already downloaded
159 public void addCacheListener(ICacheListener listener) {
160 if (mImageCache != null) {
161 mImageCache.addCacheListener(listener);
165 public void removeCacheListener(ICacheListener listener) {
166 if (mImageCache != null) {
167 mImageCache.removeCacheListener(listener);
172 * @param key The key used to find the image to remove
174 public void removeFromCache(final String key) {
175 if (mImageCache != null) {
176 mImageCache.removeFromCache(key);
181 * Finds cached or downloads album art. Used in {@link MusicPlaybackService}
182 * to set the current album art in the notification and lock screen
184 * @param albumName The name of the current album
185 * @param albumId The ID of the current album
186 * @param artistName The album artist in case we should have to download
188 * @param smallArtwork Get the small version of the default artwork if no artwork exists
189 * @return The album art as an {@link Bitmap}
191 public BitmapWithColors getArtwork(final String albumName, final long albumId,
192 final String artistName, boolean smallArtwork) {
193 // Check the disk cache
194 Bitmap artwork = null;
195 String key = String.valueOf(albumId);
197 if (artwork == null && albumName != null && mImageCache != null) {
198 artwork = mImageCache.getBitmapFromDiskCache(key);
200 if (artwork == null && albumId >= 0 && mImageCache != null) {
201 // Check for local artwork
202 artwork = mImageCache.getArtworkFromFile(mContext, albumId);
204 if (artwork != null) {
205 return new BitmapWithColors(artwork, key.hashCode());
208 return LetterTileDrawable.createDefaultBitmap(mContext, key, ImageType.ALBUM, false,
213 * Generates key used by album art cache. It needs both album name and artist name
214 * to let to select correct image for the case when there are two albums with the
217 * @param albumName The album name the cache key needs to be generated.
218 * @param artistName The artist name the cache key needs to be generated.
221 public static String generateAlbumCacheKey(final String albumName, final String artistName) {
222 if (albumName == null || artistName == null) {
225 return albumName + "_" + artistName + "_" + Config.ALBUM_ART_SUFFIX;
229 * Decode and sample down a {@link Bitmap} from a Uri.
231 * @param selectedImage Uri of the Image to decode
232 * @return A {@link Bitmap} sampled down from the original with the same
233 * aspect ratio and dimensions that are equal to or greater than the
234 * requested width and height
236 public static Bitmap decodeSampledBitmapFromUri(ContentResolver cr, final Uri selectedImage) {
237 // First decode with inJustDecodeBounds=true to check dimensions
238 final BitmapFactory.Options options = new BitmapFactory.Options();
239 options.inJustDecodeBounds = true;
242 InputStream input = cr.openInputStream(selectedImage);
243 BitmapFactory.decodeStream(input, null, options);
246 if (options.outHeight == -1 || options.outWidth == -1) {
250 // Calculate inSampleSize
251 options.inSampleSize = calculateInSampleSize(options, DEFAULT_MAX_IMAGE_WIDTH,
252 DEFAULT_MAX_IMAGE_HEIGHT);
254 // Decode bitmap with inSampleSize set
255 options.inJustDecodeBounds = false;
256 input = cr.openInputStream(selectedImage);
257 return BitmapFactory.decodeStream(input, null, options);
258 } catch (FileNotFoundException e) {
261 } catch (IOException e) {
269 * Calculate an inSampleSize for use in a
270 * {@link android.graphics.BitmapFactory.Options} object when decoding
271 * bitmaps using the decode* methods from {@link BitmapFactory}. This
272 * implementation calculates the closest inSampleSize that will result in
273 * the final decoded bitmap having a width and height equal to or larger
274 * than the requested width and height. This implementation does not ensure
275 * a power of 2 is returned for inSampleSize which can be faster when
276 * decoding but results in a larger bitmap which isn't as useful for caching
279 * @param options An options object with out* params already populated (run
280 * through a decode* method with inJustDecodeBounds==true
281 * @param reqWidth The requested width of the resulting bitmap
282 * @param reqHeight The requested height of the resulting bitmap
283 * @return The value to be used for inSampleSize
285 public static final int calculateInSampleSize(final BitmapFactory.Options options,
286 final int reqWidth, final int reqHeight) {
287 /* Raw height and width of image */
288 final int height = options.outHeight;
289 final int width = options.outWidth;
290 int inSampleSize = 1;
292 if (height > reqHeight || width > reqWidth) {
293 if (width > height) {
294 inSampleSize = Math.round((float)height / (float)reqHeight);
296 inSampleSize = Math.round((float)width / (float)reqWidth);
299 // This offers some additional logic in case the image has a strange
300 // aspect ratio. For example, a panorama may have a much larger
301 // width than height. In these cases the total pixels might still
302 // end up being too large to fit comfortably in memory, so we should
303 // be more aggressive with sample down the image (=larger
306 final float totalPixels = width * height;
308 /* More than 2x the requested pixels we'll sample down further */
309 final float totalReqPixelsCap = reqWidth * reqHeight * 2;
311 while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {