OSDN Git Service

Automatic translation import
[android-x86/packages-apps-Eleven.git] / src / com / cyngn / eleven / cache / ImageWorker.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.cyngn.eleven.cache;
13
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;
24
25 import com.cyngn.eleven.R;
26 import com.cyngn.eleven.utils.ApolloUtils;
27 import com.cyngn.eleven.utils.ThemeUtils;
28
29 import java.lang.ref.WeakReference;
30 import java.util.concurrent.RejectedExecutionException;
31
32 /**
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
36  * placeholder image.
37  */
38 public abstract class ImageWorker {
39
40     /**
41      * Default transition drawable fade time
42      */
43     private static final int FADE_IN_TIME = 200;
44
45     /**
46      * Default artwork
47      */
48     private final BitmapDrawable mDefaultArtwork;
49
50     /**
51      * The resources to use
52      */
53     private final Resources mResources;
54
55     /**
56      * First layer of the transition drawable
57      */
58     private final ColorDrawable mCurrentDrawable;
59
60     /**
61      * Layer drawable used to cross fade the result from the worker
62      */
63     private final Drawable[] mArrayDrawable;
64
65     /**
66      * Default album art
67      */
68     private final Bitmap mDefault;
69
70     /**
71      * The Context to use
72      */
73     protected Context mContext;
74
75     /**
76      * Disk and memory caches
77      */
78     protected ImageCache mImageCache;
79
80     /**
81      * Constructor of <code>ImageWorker</code>
82      *
83      * @param context The {@link Context} to use
84      */
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.
101     }
102
103     /**
104      * Set the {@link ImageCache} object to use with this ImageWorker.
105      *
106      * @param cacheCallback new {@link ImageCache} object.
107      */
108     public void setImageCache(final ImageCache cacheCallback) {
109         mImageCache = cacheCallback;
110     }
111
112     /**
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
115      * thread.
116      */
117     public void close() {
118         if (mImageCache != null) {
119             mImageCache.close();
120         }
121     }
122
123     /**
124      * flush() is called to synchronize up other methods that are accessing the
125      * cache first
126      */
127     public void flush() {
128         if (mImageCache != null) {
129             mImageCache.flush();
130         }
131     }
132
133     /**
134      * Adds a new image to the memory and disk caches
135      *
136      * @param data The key used to store the image
137      * @param bitmap The {@link Bitmap} to cache
138      */
139     public void addBitmapToCache(final String key, final Bitmap bitmap) {
140         if (mImageCache != null) {
141             mImageCache.addBitmapToCache(key, bitmap);
142         }
143     }
144
145     /**
146      * @return The deafult artwork
147      */
148     public Bitmap getDefaultArtwork() {
149         return mDefault;
150     }
151
152     /**
153      * The actual {@link AsyncTask} that will process the image.
154      */
155     private final class BitmapWorkerTask extends AsyncTask<String, Void, TransitionDrawable> {
156
157         /**
158          * The {@link ImageView} used to set the result
159          */
160         private final WeakReference<ImageView> mImageReference;
161
162         /**
163          * Type of URL to download
164          */
165         private final ImageType mImageType;
166
167         /**
168          * The key used to store cached entries
169          */
170         private String mKey;
171
172         /**
173          * Artist name param
174          */
175         private String mArtistName;
176
177         /**
178          * Album name parm
179          */
180         private String mAlbumName;
181
182         /**
183          * The album ID used to find the corresponding artwork
184          */
185         private long mAlbumId;
186
187         /**
188          * The URL of an image to download
189          */
190         private String mUrl;
191
192         /**
193          * Constructor of <code>BitmapWorkerTask</code>
194          *
195          * @param imageView The {@link ImageView} to use.
196          * @param imageType The type of image URL to fetch for.
197          */
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;
203         }
204
205         /**
206          * {@inheritDoc}
207          */
208         @Override
209         protected TransitionDrawable doInBackground(final String... params) {
210             // Define the key
211             mKey = params[0];
212
213             // The result
214             Bitmap bitmap = null;
215
216             // First, check the disk cache for the image
217             if (mKey != null && mImageCache != null && !isCancelled()
218                     && getAttachedImageView() != null) {
219                 bitmap = mImageCache.getCachedBitmap(mKey);
220             }
221
222             // Define the album id now
223             mAlbumId = Long.valueOf(params[3]);
224
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);
230             }
231
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);
239                 if (mUrl != null) {
240                     bitmap = processBitmap(mUrl);
241                 }
242             }
243
244             // Fourth, add the new image to the cache
245             if (bitmap != null && mKey != null && mImageCache != null) {
246                 addBitmapToCache(mKey, bitmap);
247             }
248
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;
255
256                 // Finally, return the image
257                 final TransitionDrawable result = new TransitionDrawable(mArrayDrawable);
258                 result.setCrossFadeEnabled(true);
259                 result.startTransition(FADE_IN_TIME);
260                 return result;
261             }
262             return null;
263         }
264
265         /**
266          * {@inheritDoc}
267          */
268         @Override
269         protected void onPostExecute(TransitionDrawable result) {
270             if (isCancelled()) {
271                 result = null;
272             }
273             final ImageView imageView = getAttachedImageView();
274             if (result != null && imageView != null) {
275                 imageView.setImageDrawable(result);
276             }
277         }
278
279         /**
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.
283          */
284         private final ImageView getAttachedImageView() {
285             final ImageView imageView = mImageReference.get();
286             final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
287             if (this == bitmapWorkerTask) {
288                 return imageView;
289             }
290             return null;
291         }
292     }
293
294     /**
295      * Calls {@code cancel()} in the worker task
296      *
297      * @param imageView the {@link ImageView} to use
298      */
299     public static final void cancelWork(final ImageView imageView) {
300         final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
301         if (bitmapWorkerTask != null) {
302             bitmapWorkerTask.cancel(true);
303         }
304     }
305
306     /**
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.
310      */
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);
317             } else {
318                 // The same work is already in progress
319                 return false;
320             }
321         }
322         return true;
323     }
324
325     /**
326      * Used to determine if the current image drawable has an instance of
327      * {@link BitmapWorkerTask}
328      *
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.
332      */
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();
339             }
340         }
341         return null;
342     }
343
344     /**
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.
350      */
351     private static final class AsyncDrawable extends ColorDrawable {
352
353         private final WeakReference<BitmapWorkerTask> mBitmapWorkerTaskReference;
354
355         /**
356          * Constructor of <code>AsyncDrawable</code>
357          */
358         public AsyncDrawable(final Resources res, final Bitmap bitmap,
359                 final BitmapWorkerTask mBitmapWorkerTask) {
360             super(Color.TRANSPARENT);
361             mBitmapWorkerTaskReference = new WeakReference<BitmapWorkerTask>(mBitmapWorkerTask);
362         }
363
364         /**
365          * @return The {@link BitmapWorkerTask} associated with this drawable
366          */
367         public BitmapWorkerTask getBitmapWorkerTask() {
368             return mBitmapWorkerTaskReference.get();
369         }
370     }
371
372     /**
373      * Called to fetch the artist or ablum art.
374      *
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
380      *            {@link Bitmap}.
381      * @param imageType The type of image URL to fetch for.
382      */
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) {
386             return;
387         }
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,
398                     bitmapWorkerTask);
399             imageView.setImageDrawable(asyncDrawable);
400             try {
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());
406             }
407         }
408     }
409
410     /**
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.
414      *
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}.
418      */
419     protected abstract Bitmap processBitmap(String key);
420
421     /**
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}.
424      *
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.
429      */
430     protected abstract String processImageUrl(String artistName, String albumName,
431             ImageType imageType);
432
433     /**
434      * Used to define what type of image URL to fetch for, artist or album.
435      */
436     public enum ImageType {
437         ARTIST, ALBUM;
438     }
439
440 }