OSDN Git Service

Automatic translation import
[android-x86/packages-apps-Eleven.git] / src / com / andrew / apollo / cache / ImageFetcher.java
1 /*
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.
10  */
11
12 package com.andrew.apollo.cache;
13
14 import android.content.Context;
15 import android.graphics.Bitmap;
16 import android.graphics.BitmapFactory;
17 import android.text.TextUtils;
18 import android.widget.ImageView;
19
20 import com.andrew.apollo.Config;
21 import com.andrew.apollo.MusicPlaybackService;
22 import com.andrew.apollo.lastfm.Album;
23 import com.andrew.apollo.lastfm.Artist;
24 import com.andrew.apollo.lastfm.MusicEntry;
25 import com.andrew.apollo.lastfm.ImageSize;
26 import com.andrew.apollo.utils.MusicUtils;
27 import com.andrew.apollo.utils.PreferenceUtils;
28
29 import java.io.BufferedInputStream;
30 import java.io.BufferedOutputStream;
31 import java.io.File;
32 import java.io.FileOutputStream;
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.net.HttpURLConnection;
36 import java.net.URL;
37
38 /**
39  * A subclass of {@link ImageWorker} that fetches images from a URL.
40  */
41 public class ImageFetcher extends ImageWorker {
42
43     public static final int IO_BUFFER_SIZE_BYTES = 1024;
44
45     private static final int DEFAULT_MAX_IMAGE_HEIGHT = 1024;
46
47     private static final int DEFAULT_MAX_IMAGE_WIDTH = 1024;
48
49     private static final String DEFAULT_HTTP_CACHE_DIR = "http"; //$NON-NLS-1$
50
51     private static ImageFetcher sInstance = null;
52
53     /**
54      * Creates a new instance of {@link ImageFetcher}.
55      *
56      * @param context The {@link Context} to use.
57      */
58     public ImageFetcher(final Context context) {
59         super(context);
60     }
61
62     /**
63      * Used to create a singleton of the image fetcher
64      *
65      * @param context The {@link Context} to use
66      * @return A new instance of this class.
67      */
68     public static final ImageFetcher getInstance(final Context context) {
69         if (sInstance == null) {
70             sInstance = new ImageFetcher(context.getApplicationContext());
71         }
72         return sInstance;
73     }
74
75     /**
76      * {@inheritDoc}
77      */
78     @Override
79     protected Bitmap processBitmap(final String url) {
80         if (url == null) {
81             return null;
82         }
83         final File file = downloadBitmapToFile(mContext, url, DEFAULT_HTTP_CACHE_DIR);
84         if (file != null) {
85             // Return a sampled down version
86             final Bitmap bitmap = decodeSampledBitmapFromFile(file.toString());
87             file.delete();
88             if (bitmap != null) {
89                 return bitmap;
90             }
91         }
92         return null;
93     }
94
95     private static String getBestImage(MusicEntry e) {
96         final ImageSize[] QUALITY = {ImageSize.EXTRALARGE, ImageSize.LARGE, ImageSize.MEDIUM,
97                 ImageSize.SMALL, ImageSize.UNKNOWN};
98         for(ImageSize q : QUALITY) {
99             String url = e.getImageURL(q);
100             if (url != null) {
101                 return url;
102             }
103         }
104         return null;
105     }
106
107     /**
108      * {@inheritDoc}
109      */
110     @Override
111     protected String processImageUrl(final String artistName, final String albumName,
112             final ImageType imageType) {
113         switch (imageType) {
114             case ARTIST:
115                 if (!TextUtils.isEmpty(artistName)) {
116                     if (PreferenceUtils.getInstance(mContext).downloadMissingArtistImages()) {
117                         final Artist artist = Artist.getInfo(mContext, artistName);
118                         if (artist != null) {
119                             return getBestImage(artist);
120                         }
121                     }
122                 }
123                 break;
124             case ALBUM:
125                 if (!TextUtils.isEmpty(artistName) && !TextUtils.isEmpty(albumName)) {
126                     if (PreferenceUtils.getInstance(mContext).downloadMissingArtwork()) {
127                         final Artist correction = Artist.getCorrection(mContext, artistName);
128                         if (correction != null) {
129                             final Album album = Album.getInfo(mContext, correction.getName(),
130                                     albumName);
131                             if (album != null) {
132                                 return getBestImage(album);
133                             }
134                         }
135                     }
136                 }
137                 break;
138             default:
139                 break;
140         }
141         return null;
142     }
143
144     /**
145      * Used to fetch album images.
146      */
147     public void loadAlbumImage(final String artistName, final String albumName, final long albumId,
148             final ImageView imageView) {
149         loadImage(generateAlbumCacheKey(albumName, artistName), artistName, albumName, albumId, imageView,
150                 ImageType.ALBUM);
151     }
152
153     /**
154      * Used to fetch the current artwork.
155      */
156     public void loadCurrentArtwork(final ImageView imageView) {
157         loadImage(generateAlbumCacheKey(MusicUtils.getAlbumName(), MusicUtils.getArtistName()),
158                 MusicUtils.getArtistName(), MusicUtils.getAlbumName(), MusicUtils.getCurrentAlbumId(),
159                 imageView, ImageType.ALBUM);
160     }
161
162     /**
163      * Used to fetch artist images.
164      */
165     public void loadArtistImage(final String key, final ImageView imageView) {
166         loadImage(key, key, null, -1, imageView, ImageType.ARTIST);
167     }
168
169     /**
170      * Used to fetch the current artist image.
171      */
172     public void loadCurrentArtistImage(final ImageView imageView) {
173         loadImage(MusicUtils.getArtistName(), MusicUtils.getArtistName(), null, -1, imageView,
174                 ImageType.ARTIST);
175     }
176
177     /**
178      * @param pause True to temporarily pause the disk cache, false otherwise.
179      */
180     public void setPauseDiskCache(final boolean pause) {
181         if (mImageCache != null) {
182             mImageCache.setPauseDiskCache(pause);
183         }
184     }
185
186     /**
187      * Clears the disk and memory caches
188      */
189     public void clearCaches() {
190         if (mImageCache != null) {
191             mImageCache.clearCaches();
192         }
193     }
194
195     /**
196      * @param key The key used to find the image to remove
197      */
198     public void removeFromCache(final String key) {
199         if (mImageCache != null) {
200             mImageCache.removeFromCache(key);
201         }
202     }
203
204     /**
205      * @param key The key used to find the image to return
206      */
207     public Bitmap getCachedBitmap(final String key) {
208         if (mImageCache != null) {
209             return mImageCache.getCachedBitmap(key);
210         }
211         return getDefaultArtwork();
212     }
213
214     /**
215      * @param keyAlbum The key (album name) used to find the album art to return
216      * @param keyArtist The key (artist name) used to find the album art to return
217      */
218     public Bitmap getCachedArtwork(final String keyAlbum, final String keyArtist) {
219         return getCachedArtwork(keyAlbum, keyArtist,
220                 MusicUtils.getIdForAlbum(mContext, keyAlbum, keyArtist));
221     }
222
223     /**
224      * @param keyAlbum The key (album name) used to find the album art to return
225      * @param keyArtist The key (artist name) used to find the album art to return
226      * @param keyId The key (album id) used to find the album art to return
227      */
228     public Bitmap getCachedArtwork(final String keyAlbum, final String keyArtist,
229             final long keyId) {
230         if (mImageCache != null) {
231             return mImageCache.getCachedArtwork(mContext,
232                     generateAlbumCacheKey(keyAlbum, keyArtist),
233                     keyId);
234         }
235         return getDefaultArtwork();
236     }
237
238     /**
239      * Finds cached or downloads album art. Used in {@link MusicPlaybackService}
240      * to set the current album art in the notification and lock screen
241      *
242      * @param albumName The name of the current album
243      * @param albumId The ID of the current album
244      * @param artistName The album artist in case we should have to download
245      *            missing artwork
246      * @return The album art as an {@link Bitmap}
247      */
248     public Bitmap getArtwork(final String albumName, final long albumId, final String artistName) {
249         // Check the disk cache
250         Bitmap artwork = null;
251
252         if (artwork == null && albumName != null && mImageCache != null) {
253             artwork = mImageCache.getBitmapFromDiskCache(
254                     generateAlbumCacheKey(albumName, artistName));
255         }
256         if (artwork == null && albumId >= 0 && mImageCache != null) {
257             // Check for local artwork
258             artwork = mImageCache.getArtworkFromFile(mContext, albumId);
259         }
260         if (artwork != null) {
261             return artwork;
262         }
263         return getDefaultArtwork();
264     }
265
266     /**
267      * Download a {@link Bitmap} from a URL, write it to a disk and return the
268      * File pointer. This implementation uses a simple disk cache.
269      *
270      * @param context The context to use
271      * @param urlString The URL to fetch
272      * @return A {@link File} pointing to the fetched bitmap
273      */
274     public static final File downloadBitmapToFile(final Context context, final String urlString,
275             final String uniqueName) {
276         final File cacheDir = ImageCache.getDiskCacheDir(context, uniqueName);
277
278         if (!cacheDir.exists()) {
279             cacheDir.mkdir();
280         }
281
282         HttpURLConnection urlConnection = null;
283         BufferedOutputStream out = null;
284
285         try {
286             final File tempFile = File.createTempFile("bitmap", null, cacheDir); //$NON-NLS-1$
287
288             final URL url = new URL(urlString);
289             urlConnection = (HttpURLConnection)url.openConnection();
290             if (urlConnection.getResponseCode() != HttpURLConnection.HTTP_OK) {
291                 return null;
292             }
293             final InputStream in = new BufferedInputStream(urlConnection.getInputStream(),
294                     IO_BUFFER_SIZE_BYTES);
295             out = new BufferedOutputStream(new FileOutputStream(tempFile), IO_BUFFER_SIZE_BYTES);
296
297             int oneByte;
298             while ((oneByte = in.read()) != -1) {
299                 out.write(oneByte);
300             }
301             return tempFile;
302         } catch (final IOException ignored) {
303         } finally {
304             if (urlConnection != null) {
305                 urlConnection.disconnect();
306             }
307             if (out != null) {
308                 try {
309                     out.close();
310                 } catch (final IOException ignored) {
311                 }
312             }
313         }
314         return null;
315     }
316
317     /**
318      * Decode and sample down a {@link Bitmap} from a file to the requested
319      * width and height.
320      *
321      * @param filename The full path of the file to decode
322      * @param reqWidth The requested width of the resulting bitmap
323      * @param reqHeight The requested height of the resulting bitmap
324      * @return A {@link Bitmap} sampled down from the original with the same
325      *         aspect ratio and dimensions that are equal to or greater than the
326      *         requested width and height
327      */
328     public static Bitmap decodeSampledBitmapFromFile(final String filename) {
329
330         // First decode with inJustDecodeBounds=true to check dimensions
331         final BitmapFactory.Options options = new BitmapFactory.Options();
332         options.inJustDecodeBounds = true;
333         BitmapFactory.decodeFile(filename, options);
334
335         // Calculate inSampleSize
336         options.inSampleSize = calculateInSampleSize(options, DEFAULT_MAX_IMAGE_WIDTH,
337                 DEFAULT_MAX_IMAGE_HEIGHT);
338
339         // Decode bitmap with inSampleSize set
340         options.inJustDecodeBounds = false;
341         return BitmapFactory.decodeFile(filename, options);
342     }
343
344     /**
345      * Calculate an inSampleSize for use in a
346      * {@link android.graphics.BitmapFactory.Options} object when decoding
347      * bitmaps using the decode* methods from {@link BitmapFactory}. This
348      * implementation calculates the closest inSampleSize that will result in
349      * the final decoded bitmap having a width and height equal to or larger
350      * than the requested width and height. This implementation does not ensure
351      * a power of 2 is returned for inSampleSize which can be faster when
352      * decoding but results in a larger bitmap which isn't as useful for caching
353      * purposes.
354      *
355      * @param options An options object with out* params already populated (run
356      *            through a decode* method with inJustDecodeBounds==true
357      * @param reqWidth The requested width of the resulting bitmap
358      * @param reqHeight The requested height of the resulting bitmap
359      * @return The value to be used for inSampleSize
360      */
361     public static final int calculateInSampleSize(final BitmapFactory.Options options,
362             final int reqWidth, final int reqHeight) {
363         /* Raw height and width of image */
364         final int height = options.outHeight;
365         final int width = options.outWidth;
366         int inSampleSize = 1;
367
368         if (height > reqHeight || width > reqWidth) {
369             if (width > height) {
370                 inSampleSize = Math.round((float)height / (float)reqHeight);
371             } else {
372                 inSampleSize = Math.round((float)width / (float)reqWidth);
373             }
374
375             // This offers some additional logic in case the image has a strange
376             // aspect ratio. For example, a panorama may have a much larger
377             // width than height. In these cases the total pixels might still
378             // end up being too large to fit comfortably in memory, so we should
379             // be more aggressive with sample down the image (=larger
380             // inSampleSize).
381
382             final float totalPixels = width * height;
383
384             /* More than 2x the requested pixels we'll sample down further */
385             final float totalReqPixelsCap = reqWidth * reqHeight * 2;
386
387             while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
388                 inSampleSize++;
389             }
390         }
391         return inSampleSize;
392     }
393
394     /**
395      * Generates key used by album art cache. It needs both album name and artist name
396      * to let to select correct image for the case when there are two albums with the
397      * same artist.
398      *
399      * @param albumName The album name the cache key needs to be generated.
400      * @param artistName The artist name the cache key needs to be generated.
401      * @return
402      */
403     public static String generateAlbumCacheKey(final String albumName, final String artistName) {
404         if (albumName == null || artistName == null) {
405             return null;
406         }
407         return new StringBuilder(albumName)
408                 .append("_")
409                 .append(artistName)
410                 .append("_")
411                 .append(Config.ALBUM_ART_SUFFIX)
412                 .toString();
413     }
414 }