switch to alsa.omap3 module
[android_pandora.git] / apps / oi-filemanager / FileManager / src / org / openintents / filemanager / ThumbnailLoader.java
1 package org.openintents.filemanager;
2
3 import java.lang.ref.SoftReference;
4 import java.lang.ref.WeakReference;
5 import java.util.ArrayList;
6 import java.util.LinkedHashMap;
7 import java.util.concurrent.ConcurrentHashMap;
8 import java.util.concurrent.ExecutorService;
9 import java.util.concurrent.Executors;
10
11 import org.openintents.filemanager.util.FileUtils;
12 import org.openintents.filemanager.util.ImageUtils;
13
14 import android.app.Activity;
15 import android.content.Context;
16 import android.graphics.Bitmap;
17 import android.graphics.BitmapFactory;
18 import android.graphics.Canvas;
19 import android.graphics.Matrix;
20 import android.graphics.drawable.BitmapDrawable;
21 import android.graphics.drawable.Drawable;
22 import android.os.Handler;
23 import android.util.Log;
24 import android.widget.ImageView;
25
26 public class ThumbnailLoader {
27         
28         private static final String TAG = "OIFM_ThumbnailLoader";
29         
30         // Both hard and soft caches are purged after 40 seconds idling. 
31         private static final int DELAY_BEFORE_PURGE = 40000;
32         private static final int MAX_CACHE_CAPACITY = 40;
33         
34         // Maximum number of threads in the executor pool.
35         // TODO: Tune POOL_SIZE for maximum performance gain
36         private static final int POOL_SIZE = 5;
37         
38     private boolean cancel;
39     private Context mContext;
40         
41     //private static int thumbnailWidth = 96;
42     //private static int thumbnailHeight = 129;
43     private static int thumbnailWidth = 32;
44     private static int thumbnailHeight = 32;
45     
46     private Runnable purger;
47     private Handler purgeHandler;
48     private ExecutorService mExecutor;
49     
50     // Soft bitmap cache for thumbnails removed from the hard cache.
51     // This gets cleared by the Garbage Collector everytime we get low on memory.
52     private ConcurrentHashMap<String, SoftReference<Bitmap>> mSoftBitmapCache;
53     private LinkedHashMap<String, Bitmap> mHardBitmapCache;
54     private ArrayList<String> mBlacklist;
55     
56     /**
57      * Used for loading and decoding thumbnails from files.
58      * 
59      * @author PhilipHayes
60      * @param context Current application context.
61      */
62         public ThumbnailLoader(Context context) {
63                 mContext = context;
64                 
65                 purger = new Runnable(){
66                         @Override
67                         public void run() {
68                                 Log.d(TAG, "Purge Timer hit; Clearing Caches.");
69                                 clearCaches();
70                         }
71                 };
72                 
73                 purgeHandler = new Handler();
74                 mExecutor = Executors.newFixedThreadPool(POOL_SIZE);
75                 
76                 mBlacklist = new ArrayList<String>();
77                 mSoftBitmapCache = new ConcurrentHashMap<String, SoftReference<Bitmap>>(MAX_CACHE_CAPACITY / 2);
78                 mHardBitmapCache = new LinkedHashMap<String, Bitmap>(MAX_CACHE_CAPACITY / 2, 0.75f, true){
79                         
80                         /***/
81                         private static final long serialVersionUID = 1347795807259717646L;
82                         
83                         @Override
84                         protected boolean removeEldestEntry(LinkedHashMap.Entry<String, Bitmap> eldest){
85                                 // Moves the last used item in the hard cache to the soft cache.
86                                 if(size() > MAX_CACHE_CAPACITY){
87                                         mSoftBitmapCache.put(eldest.getKey(), new SoftReference<Bitmap>(eldest.getValue()));
88                                         return true;
89                                 } else {
90                                         return false;
91                                 }
92                         }
93                 };
94         }  
95
96         public static void setThumbnailHeight(int height) {
97                 thumbnailHeight = height;
98                 thumbnailWidth = height * 4 / 3;
99         }
100         
101         /**
102          * 
103          * @param parentFile The current directory.
104          * @param text The IconifiedText container.
105          * @param imageView The ImageView from the IconifiedTextView.
106          */
107         public void loadImage(String parentFile, IconifiedText text, ImageView imageView) {
108                 if(!cancel && !mBlacklist.contains(text.getText())){
109                         // We reset the caches after every 30 or so seconds of inactivity for memory efficiency.
110                         resetPurgeTimer();
111                         
112                         Bitmap bitmap = getBitmapFromCache(text.getText());
113                         if(bitmap != null){
114                                 // We're still in the UI thread so we just update the icons from here.
115                                 imageView.setImageBitmap(bitmap);
116                                 text.setIcon(bitmap);
117                         } else {
118                                 if (!cancel) {
119                                         // Submit the file for decoding.
120                                         Thumbnail thumbnail = new Thumbnail(parentFile, imageView, text);
121                                         WeakReference<ThumbnailRunner> runner = new WeakReference<ThumbnailRunner>(new ThumbnailRunner(thumbnail));
122                                         mExecutor.submit(runner.get());
123                                 }
124                         }
125                 }
126         }
127         /**
128          * Cancels any downloads, shuts down the executor pool,
129          * and then purges the caches.
130          */
131         public void cancel(){
132                 cancel = true;
133                 
134                 // We could also terminate it immediately,
135                 // but that may lead to synchronization issues.
136                 if(!mExecutor.isShutdown()){
137                         mExecutor.shutdown();
138                 }
139                 
140                 stopPurgeTimer();
141                 
142                 mContext = null;
143                 clearCaches();
144         }
145         
146         /**
147          * Stops the cache purger from running until it is reset again.
148          */
149         public void stopPurgeTimer(){
150                 purgeHandler.removeCallbacks(purger);
151         }
152         
153         /**
154          * Purges the cache every (DELAY_BEFORE_PURGE) milliseconds.
155          * @see DELAY_BEFORE_PURGE
156          */
157         private void resetPurgeTimer() {
158                 purgeHandler.removeCallbacks(purger);
159                 purgeHandler.postDelayed(purger, DELAY_BEFORE_PURGE);
160         }
161         
162         private void clearCaches(){
163                 mSoftBitmapCache.clear();
164                 mHardBitmapCache.clear();
165                 mBlacklist.clear();
166         }
167         
168         /**
169          * @param key In this case the file name (used as the mapping id).
170          * @return bitmap The cached bitmap or null if it could not be located.
171          * 
172          * As the name suggests, this method attemps to obtain a bitmap stored
173          * in one of the caches. First it checks the hard cache for the key.
174          * If a key is found, it moves the cached bitmap to the head of the cache
175          * so it gets moved to the soft cache last.
176          * 
177          * If the hard cache doesn't contain the bitmap, it checks the soft cache
178          * for the cached bitmap. If neither of the caches contain the bitmap, this
179          * returns null.
180          */
181         private Bitmap getBitmapFromCache(String key){
182                 synchronized(mHardBitmapCache) {
183                         Bitmap bitmap = mHardBitmapCache.get(key);
184                         if(bitmap != null){
185                                 // Put bitmap on top of cache so it's purged last.
186                                 mHardBitmapCache.remove(key);
187                                 mHardBitmapCache.put(key, bitmap);
188                                 return bitmap;
189                         }
190                 }
191                 
192                 SoftReference<Bitmap> bitmapRef = mSoftBitmapCache.get(key);
193                 if(bitmapRef != null){
194                         Bitmap bitmap = bitmapRef.get();
195                         if(bitmap != null){
196                                 return bitmap;
197                         } else {
198                                 // Must have been collected by the Garbage Collector 
199                                 // so we remove the bucket from the cache.
200                                 mSoftBitmapCache.remove(key);
201                         }
202                 }
203                 
204                 // Could not locate the bitmap in any of the caches, so we return null.
205                 return null;
206         }
207         
208         /**
209          * @param parentFile The parentFile, so we can obtain the full path of the bitmap
210          * @param fileName The name of the file, also the text in the list item.
211          * @return The resized and resampled bitmap, if can not be decoded it returns null.
212          */
213         private Bitmap decodeFile(String parentFile, String fileName) {
214                 if(!cancel){
215                         try {
216                                 BitmapFactory.Options options = new BitmapFactory.Options();
217                                 
218                                 options.inJustDecodeBounds = true;
219                                 options.outWidth = 0;
220                                 options.outHeight = 0;
221                                 options.inSampleSize = 1;
222                                 
223                                 String filePath = FileUtils.getFile(parentFile, fileName).getPath();
224                 
225                                 BitmapFactory.decodeFile(filePath, options);
226                                 
227                                 if(options.outWidth > 0 && options.outHeight > 0){
228                                         if (!cancel) {
229                                                 // Now see how much we need to scale it down.
230                                                 int widthFactor = (options.outWidth + thumbnailWidth - 1)
231                                                                 / thumbnailWidth;
232                                                 int heightFactor = (options.outHeight + thumbnailHeight - 1)
233                                                                 / thumbnailHeight;
234                                                 widthFactor = Math.max(widthFactor, heightFactor);
235                                                 widthFactor = Math.max(widthFactor, 1);
236                                                 // Now turn it into a power of two.
237                                                 if (widthFactor > 1) {
238                                                         if ((widthFactor & (widthFactor - 1)) != 0) {
239                                                                 while ((widthFactor & (widthFactor - 1)) != 0) {
240                                                                         widthFactor &= widthFactor - 1;
241                                                                 }
242
243                                                                 widthFactor <<= 1;
244                                                         }
245                                                 }
246                                                 options.inSampleSize = widthFactor;
247                                                 options.inJustDecodeBounds = false;
248                                                 Bitmap bitmap = ImageUtils.resizeBitmap(
249                                                                 BitmapFactory.decodeFile(filePath, options),
250                                                                 72, 72);
251                                                 if (bitmap != null) {
252                                                         return bitmap;
253                                                 }
254                                         }
255                                 } else {
256                                         // Must not be a bitmap, so we add it to the blacklist.
257                                         if(!mBlacklist.contains(fileName)){
258                                                 mBlacklist.add(fileName);
259                                         }
260                                 }
261                         } catch(Exception e) { }
262                 }
263                 return null;
264         }
265         
266         /**
267          * Holder object for thumbnail information.
268          */
269         private class Thumbnail {
270                 public String parentFile;
271                 public ImageView imageView;
272                 public IconifiedText text;
273                 
274                 public Thumbnail(String parentFile, ImageView imageView, IconifiedText text) {
275                         this.parentFile = parentFile;
276                         this.imageView = imageView;
277                         this.text = text;
278                 }
279         }
280         
281         /**
282          * Decodes the bitmap and sends a ThumbnailUpdater on the UI Thread
283          * to update the listitem and iconified text.
284          * 
285          * @see ThumbnailUpdater
286          */
287         private class ThumbnailRunner implements Runnable {
288                 Thumbnail thumb;
289                 ThumbnailRunner(Thumbnail thumb){
290                         this.thumb = thumb;
291                 }
292                 
293                 @Override
294                 public void run() {
295                         if(!cancel){
296                                 Bitmap bitmap = decodeFile(thumb.parentFile, thumb.text.getText());
297                                 if(bitmap != null && !cancel){
298                                         // Bitmap was successfully decoded so we place it in the hard cache.
299                                         mHardBitmapCache.put(thumb.text.getText(), bitmap);
300                                         Activity activity = ((Activity) mContext);
301                                         activity.runOnUiThread(new ThumbnailUpdater(bitmap, thumb));
302                                         thumb = null;
303                                 }
304                         }
305                 }
306         }
307         
308         /**
309          * When run on the UI Thread, this updates the 
310          * thumbnail in the corresponding iconifiedtext and imageview.
311          */
312         private class ThumbnailUpdater implements Runnable {
313                 private Bitmap bitmap;
314                 private Thumbnail thumb;
315                 
316                 public ThumbnailUpdater(Bitmap bitmap, Thumbnail thumb) {
317                         this.bitmap = bitmap;
318                         this.thumb = thumb;
319                 }
320                 
321                 @Override
322                 public void run() {
323                         if(bitmap != null && mContext != null && !cancel){
324                                 thumb.imageView.setImageBitmap(bitmap);
325                                 thumb.text.setIcon(bitmap);
326                         }
327                 }
328         }
329 }