switch to alsa.omap3 module
[android_pandora.git] / apps / oi-filemanager / FileManager / src / org / openintents / filemanager / ThumbnailLoader.java
CommitLineData
811a5a4a 1package org.openintents.filemanager;
2
3import java.lang.ref.SoftReference;
4import java.lang.ref.WeakReference;
5import java.util.ArrayList;
6import java.util.LinkedHashMap;
7import java.util.concurrent.ConcurrentHashMap;
8import java.util.concurrent.ExecutorService;
9import java.util.concurrent.Executors;
10
11import org.openintents.filemanager.util.FileUtils;
12import org.openintents.filemanager.util.ImageUtils;
13
14import android.app.Activity;
15import android.content.Context;
16import android.graphics.Bitmap;
17import android.graphics.BitmapFactory;
18import android.graphics.Canvas;
19import android.graphics.Matrix;
20import android.graphics.drawable.BitmapDrawable;
21import android.graphics.drawable.Drawable;
22import android.os.Handler;
23import android.util.Log;
24import android.widget.ImageView;
25
26public 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}