- moved rotateImage() to BitmapUtils
[pub/Android/ownCloud.git] / src / com / owncloud / android / datamodel / ThumbnailsCacheManager.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012-2014 ownCloud Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18 package com.owncloud.android.datamodel;
19
20 import java.io.File;
21 import java.lang.ref.WeakReference;
22
23 import android.content.res.Resources;
24 import android.graphics.Bitmap;
25 import android.graphics.BitmapFactory;
26 import android.graphics.Matrix;
27 import android.graphics.Bitmap.CompressFormat;
28 import android.graphics.drawable.BitmapDrawable;
29 import android.graphics.drawable.Drawable;
30 import android.media.ExifInterface;
31 import android.media.ThumbnailUtils;
32 import android.os.AsyncTask;
33 import android.util.TypedValue;
34 import android.widget.ImageView;
35
36 import com.owncloud.android.MainApp;
37 import com.owncloud.android.lib.common.utils.Log_OC;
38 import com.owncloud.android.ui.adapter.DiskLruImageCache;
39 import com.owncloud.android.utils.BitmapUtils;
40 import com.owncloud.android.utils.DisplayUtils;
41
42 /**
43 * Manager for concurrent access to thumbnails cache.
44 *
45 * @author Tobias Kaminsky
46 * @author David A. Velasco
47 */
48 public class ThumbnailsCacheManager {
49
50 private static final String TAG = ThumbnailsCacheManager.class.getSimpleName();
51
52 private static final String CACHE_FOLDER = "thumbnailCache";
53
54 private static final Object mThumbnailsDiskCacheLock = new Object();
55 private static DiskLruImageCache mThumbnailCache = null;
56 private static boolean mThumbnailCacheStarting = true;
57
58 private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
59 private static final CompressFormat mCompressFormat = CompressFormat.JPEG;
60 private static final int mCompressQuality = 70;
61
62 public static Bitmap mDefaultImg =
63 BitmapFactory.decodeResource(
64 MainApp.getAppContext().getResources(),
65 DisplayUtils.getResourceId("image/png", "default.png")
66 );
67
68
69 public static class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
70 @Override
71 protected Void doInBackground(File... params) {
72 synchronized (mThumbnailsDiskCacheLock) {
73 mThumbnailCacheStarting = true;
74 if (mThumbnailCache == null) {
75 try {
76 // Check if media is mounted or storage is built-in, if so,
77 // try and use external cache dir; otherwise use internal cache dir
78 final String cachePath =
79 MainApp.getAppContext().getExternalCacheDir().getPath() +
80 File.separator + CACHE_FOLDER;
81 Log_OC.d(TAG, "create dir: " + cachePath);
82 final File diskCacheDir = new File(cachePath);
83 mThumbnailCache = new DiskLruImageCache(
84 diskCacheDir,
85 DISK_CACHE_SIZE,
86 mCompressFormat,
87 mCompressQuality
88 );
89 } catch (Exception e) {
90 Log_OC.d(TAG, "Thumbnail cache could not be opened ", e);
91 mThumbnailCache = null;
92 }
93 }
94 mThumbnailCacheStarting = false; // Finished initialization
95 mThumbnailsDiskCacheLock.notifyAll(); // Wake any waiting threads
96 }
97 return null;
98 }
99 }
100
101
102 public static void addBitmapToCache(String key, Bitmap bitmap) {
103 synchronized (mThumbnailsDiskCacheLock) {
104 if (mThumbnailCache != null) {
105 mThumbnailCache.put(key, bitmap);
106 }
107 }
108 }
109
110
111 public static Bitmap getBitmapFromDiskCache(String key) {
112 synchronized (mThumbnailsDiskCacheLock) {
113 // Wait while disk cache is started from background thread
114 while (mThumbnailCacheStarting) {
115 try {
116 mThumbnailsDiskCacheLock.wait();
117 } catch (InterruptedException e) {}
118 }
119 if (mThumbnailCache != null) {
120 return (Bitmap) mThumbnailCache.getBitmap(key);
121 }
122 }
123 return null;
124 }
125
126
127 public static boolean cancelPotentialWork(OCFile file, ImageView imageView) {
128 final ThumbnailGenerationTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
129
130 if (bitmapWorkerTask != null) {
131 final OCFile bitmapData = bitmapWorkerTask.mFile;
132 // If bitmapData is not yet set or it differs from the new data
133 if (bitmapData == null || bitmapData != file) {
134 // Cancel previous task
135 bitmapWorkerTask.cancel(true);
136 } else {
137 // The same work is already in progress
138 return false;
139 }
140 }
141 // No task associated with the ImageView, or an existing task was cancelled
142 return true;
143 }
144
145 public static ThumbnailGenerationTask getBitmapWorkerTask(ImageView imageView) {
146 if (imageView != null) {
147 final Drawable drawable = imageView.getDrawable();
148 if (drawable instanceof AsyncDrawable) {
149 final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
150 return asyncDrawable.getBitmapWorkerTask();
151 }
152 }
153 return null;
154 }
155
156 public static class ThumbnailGenerationTask extends AsyncTask<OCFile, Void, Bitmap> {
157 private final WeakReference<ImageView> mImageViewReference;
158 private OCFile mFile;
159 private FileDataStorageManager mStorageManager;
160
161 public ThumbnailGenerationTask(ImageView imageView, FileDataStorageManager storageManager) {
162 // Use a WeakReference to ensure the ImageView can be garbage collected
163 mImageViewReference = new WeakReference<ImageView>(imageView);
164 if (storageManager == null)
165 throw new IllegalArgumentException("storageManager must not be NULL");
166 mStorageManager = storageManager;
167 }
168
169 // Decode image in background.
170 @Override
171 protected Bitmap doInBackground(OCFile... params) {
172 Bitmap thumbnail = null;
173
174 try {
175 mFile = params[0];
176 final String imageKey = String.valueOf(mFile.getRemoteId());
177
178 // Check disk cache in background thread
179 thumbnail = getBitmapFromDiskCache(imageKey);
180
181 // Not found in disk cache
182 if (thumbnail == null || mFile.needsUpdateThumbnail()) {
183 // Converts dp to pixel
184 Resources r = MainApp.getAppContext().getResources();
185 int px = (int) Math.round(TypedValue.applyDimension(
186 TypedValue.COMPLEX_UNIT_DIP, 150, r.getDisplayMetrics()
187 ));
188
189 if (mFile.isDown()){
190 Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(
191 mFile.getStoragePath(), px, px);
192
193 if (bitmap != null) {
194 thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
195
196 // Rotate image, obeying exif tag
197 thumbnail = BitmapUtils.rotateImage(thumbnail, mFile.getStoragePath());
198
199 // Add thumbnail to cache
200 addBitmapToCache(imageKey, thumbnail);
201
202 mFile.setNeedsUpdateThumbnail(false);
203 mStorageManager.saveFile(mFile);
204 }
205
206 }
207 }
208
209 } catch (Throwable t) {
210 // the app should never break due to a problem with thumbnails
211 Log_OC.e(TAG, "Generation of thumbnail for " + mFile + " failed", t);
212 if (t instanceof OutOfMemoryError) {
213 System.gc();
214 }
215 }
216
217 return thumbnail;
218 }
219
220 protected void onPostExecute(Bitmap bitmap){
221 if (isCancelled()) {
222 bitmap = null;
223 }
224
225 if (mImageViewReference != null && bitmap != null) {
226 final ImageView imageView = mImageViewReference.get();
227 final ThumbnailGenerationTask bitmapWorkerTask =
228 getBitmapWorkerTask(imageView);
229 if (this == bitmapWorkerTask && imageView != null) {
230 if (imageView.getTag().equals(mFile.getFileId())) {
231 imageView.setImageBitmap(bitmap);
232 }
233 }
234 }
235 }
236 }
237
238
239 public static class AsyncDrawable extends BitmapDrawable {
240 private final WeakReference<ThumbnailGenerationTask> bitmapWorkerTaskReference;
241
242 public AsyncDrawable(
243 Resources res, Bitmap bitmap, ThumbnailGenerationTask bitmapWorkerTask
244 ) {
245
246 super(res, bitmap);
247 bitmapWorkerTaskReference =
248 new WeakReference<ThumbnailGenerationTask>(bitmapWorkerTask);
249 }
250
251 public ThumbnailGenerationTask getBitmapWorkerTask() {
252 return bitmapWorkerTaskReference.get();
253 }
254 }
255
256
257 /**
258 * Remove from cache the remoteId passed
259 * @param fileRemoteId: remote id of mFile passed
260 */
261 public static void removeFileFromCache(String fileRemoteId){
262 synchronized (mThumbnailsDiskCacheLock) {
263 if (mThumbnailCache != null) {
264 mThumbnailCache.removeKey(fileRemoteId);
265 }
266 mThumbnailsDiskCacheLock.notifyAll(); // Wake any waiting threads
267 }
268 }
269
270 }