bf62a23c2a182297783c0f9cafadae75fe134804
[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 org.apache.commons.httpclient.HttpStatus;
24 import org.apache.commons.httpclient.methods.GetMethod;
25
26 import android.accounts.Account;
27 import android.accounts.AccountManager;
28 import android.content.res.Resources;
29 import android.graphics.Bitmap;
30 import android.graphics.Bitmap.CompressFormat;
31 import android.graphics.BitmapFactory;
32 import android.graphics.Matrix;
33 import android.graphics.drawable.BitmapDrawable;
34 import android.graphics.drawable.Drawable;
35 import android.media.ExifInterface;
36 import android.media.ThumbnailUtils;
37 import android.net.Uri;
38 import android.os.AsyncTask;
39 import android.widget.ImageView;
40
41 import com.owncloud.android.MainApp;
42 import com.owncloud.android.R;
43 import com.owncloud.android.lib.common.OwnCloudAccount;
44 import com.owncloud.android.lib.common.OwnCloudClient;
45 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
46 import com.owncloud.android.lib.common.accounts.AccountUtils.Constants;
47 import com.owncloud.android.lib.common.utils.Log_OC;
48 import com.owncloud.android.lib.resources.status.OwnCloudVersion;
49 import com.owncloud.android.ui.adapter.DiskLruImageCache;
50 import com.owncloud.android.utils.BitmapUtils;
51 import com.owncloud.android.utils.DisplayUtils;
52
53 /**
54 * Manager for concurrent access to thumbnails cache.
55 *
56 * @author Tobias Kaminsky
57 * @author David A. Velasco
58 */
59 public class ThumbnailsCacheManager {
60
61 private static final String TAG = ThumbnailsCacheManager.class.getSimpleName();
62
63 private static final String CACHE_FOLDER = "thumbnailCache";
64 private static final String MINOR_SERVER_VERSION_FOR_THUMBS = "7.8.0";
65
66 private static final Object mThumbnailsDiskCacheLock = new Object();
67 private static DiskLruImageCache mThumbnailCache = null;
68 private static boolean mThumbnailCacheStarting = true;
69
70 private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
71 private static final CompressFormat mCompressFormat = CompressFormat.JPEG;
72 private static final int mCompressQuality = 70;
73 private static OwnCloudClient mClient = null;
74 private static String mServerVersion = null;
75
76 public static Bitmap mDefaultImg =
77 BitmapFactory.decodeResource(
78 MainApp.getAppContext().getResources(),
79 DisplayUtils.getResourceId("image/png", "default.png")
80 );
81
82
83 public static class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
84
85 @Override
86 protected Void doInBackground(File... params) {
87 synchronized (mThumbnailsDiskCacheLock) {
88 mThumbnailCacheStarting = true;
89
90 if (mThumbnailCache == null) {
91 try {
92 // Check if media is mounted or storage is built-in, if so,
93 // try and use external cache dir; otherwise use internal cache dir
94 final String cachePath =
95 MainApp.getAppContext().getExternalCacheDir().getPath() +
96 File.separator + CACHE_FOLDER;
97 Log_OC.d(TAG, "create dir: " + cachePath);
98 final File diskCacheDir = new File(cachePath);
99 mThumbnailCache = new DiskLruImageCache(
100 diskCacheDir,
101 DISK_CACHE_SIZE,
102 mCompressFormat,
103 mCompressQuality
104 );
105 } catch (Exception e) {
106 Log_OC.d(TAG, "Thumbnail cache could not be opened ", e);
107 mThumbnailCache = null;
108 }
109 }
110 mThumbnailCacheStarting = false; // Finished initialization
111 mThumbnailsDiskCacheLock.notifyAll(); // Wake any waiting threads
112 }
113 return null;
114 }
115 }
116
117
118 public static void addBitmapToCache(String key, Bitmap bitmap) {
119 synchronized (mThumbnailsDiskCacheLock) {
120 if (mThumbnailCache != null) {
121 mThumbnailCache.put(key, bitmap);
122 }
123 }
124 }
125
126
127 public static Bitmap getBitmapFromDiskCache(String key) {
128 synchronized (mThumbnailsDiskCacheLock) {
129 // Wait while disk cache is started from background thread
130 while (mThumbnailCacheStarting) {
131 try {
132 mThumbnailsDiskCacheLock.wait();
133 } catch (InterruptedException e) {}
134 }
135 if (mThumbnailCache != null) {
136 return (Bitmap) mThumbnailCache.getBitmap(key);
137 }
138 }
139 return null;
140 }
141
142
143 public static boolean cancelPotentialWork(OCFile file, ImageView imageView) {
144 final ThumbnailGenerationTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
145
146 if (bitmapWorkerTask != null) {
147 final OCFile bitmapData = bitmapWorkerTask.mFile;
148 // If bitmapData is not yet set or it differs from the new data
149 if (bitmapData == null || bitmapData != file) {
150 // Cancel previous task
151 bitmapWorkerTask.cancel(true);
152 } else {
153 // The same work is already in progress
154 return false;
155 }
156 }
157 // No task associated with the ImageView, or an existing task was cancelled
158 return true;
159 }
160
161 public static boolean cancelPotentialWork(File file, ImageView imageView) {
162 final ThumbnailLocalGenerationTask bitmapWorkerTask = getBitmapLocalWorkerTask(imageView);
163
164 if (bitmapWorkerTask != null) {
165 final File bitmapData = bitmapWorkerTask.mFile;
166 // If bitmapData is not yet set or it differs from the new data
167 if (bitmapData == null || bitmapData != file) {
168 // Cancel previous task
169 bitmapWorkerTask.cancel(true);
170 } else {
171 // The same work is already in progress
172 return false;
173 }
174 }
175 // No task associated with the ImageView, or an existing task was cancelled
176 return true;
177 }
178
179 public static ThumbnailGenerationTask getBitmapWorkerTask(ImageView imageView) {
180 if (imageView != null) {
181 final Drawable drawable = imageView.getDrawable();
182 if (drawable instanceof AsyncDrawable) {
183 final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
184 return asyncDrawable.getBitmapWorkerTask();
185 }
186 }
187 return null;
188 }
189
190 public static ThumbnailLocalGenerationTask getBitmapLocalWorkerTask(ImageView imageView) {
191 if (imageView != null) {
192 final Drawable drawable = imageView.getDrawable();
193 if (drawable instanceof AsyncLocalDrawable) {
194 final AsyncLocalDrawable asyncDrawable = (AsyncLocalDrawable) drawable;
195 return asyncDrawable.getBitmapWorkerTask();
196 }
197 }
198 return null;
199 }
200
201 public static class ThumbnailLocalGenerationTask extends AsyncTask<File, Void, Bitmap> {
202 private final WeakReference<ImageView> mImageViewLocalReference;
203 private File mFile;
204
205 public ThumbnailLocalGenerationTask(ImageView imageView) {
206 // Use a WeakReference to ensure the ImageView can be garbage collected
207 mImageViewLocalReference = new WeakReference<ImageView>(imageView);
208 }
209
210 // Decode image in background.
211 @Override
212 protected Bitmap doInBackground(File... params) {
213 Bitmap thumbnail = null;
214
215 try {
216 mFile = params[0];
217 final String imageKey = String.valueOf(mFile.hashCode());
218
219 // Check disk cache in background thread
220 thumbnail = getBitmapFromDiskCache(imageKey);
221
222 // Not found in disk cache
223 if (thumbnail == null) {
224 // Converts dp to pixel
225 Resources r = MainApp.getAppContext().getResources();
226
227 int px = (int) Math.round(r.getDimension(R.dimen.file_icon_size));
228
229 Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(
230 mFile.getAbsolutePath(), px, px);
231
232 if (bitmap != null) {
233 thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
234
235 // Add thumbnail to cache
236 addBitmapToCache(imageKey, thumbnail);
237 }
238 }
239
240 } catch (Throwable t) {
241 // the app should never break due to a problem with thumbnails
242 Log_OC.e(TAG, "Generation of thumbnail for " + mFile + " failed", t);
243 if (t instanceof OutOfMemoryError) {
244 System.gc();
245 }
246 }
247
248 return thumbnail;
249 }
250
251 protected void onPostExecute(Bitmap bitmap){
252 if (isCancelled()) {
253 bitmap = null;
254 }
255
256 if (mImageViewLocalReference != null && bitmap != null) {
257 final ImageView imageView = mImageViewLocalReference.get();
258 final ThumbnailLocalGenerationTask bitmapWorkerTask = getBitmapLocalWorkerTask(imageView);
259 if (this == bitmapWorkerTask && imageView != null) {
260 if (imageView.getTag().equals(mFile.hashCode())) {
261 imageView.setImageBitmap(bitmap);
262 }
263 }
264 }
265 }
266 }
267
268 public static class ThumbnailGenerationTask extends AsyncTask<OCFile, Void, Bitmap> {
269 private final WeakReference<ImageView> mImageViewReference;
270 private static Account mAccount;
271 private OCFile mFile;
272 private FileDataStorageManager mStorageManager;
273
274 public ThumbnailGenerationTask(ImageView imageView, FileDataStorageManager storageManager, Account account) {
275 // Use a WeakReference to ensure the ImageView can be garbage collected
276 mImageViewReference = new WeakReference<ImageView>(imageView);
277 if (storageManager == null)
278 throw new IllegalArgumentException("storageManager must not be NULL");
279 mStorageManager = storageManager;
280 mAccount = account;
281 }
282
283 // Decode image in background.
284 @Override
285 protected Bitmap doInBackground(OCFile... params) {
286 Bitmap thumbnail = null;
287
288 try {
289 if (mAccount != null) {
290 AccountManager accountMgr = AccountManager.get(MainApp.getAppContext());
291
292 mServerVersion = accountMgr.getUserData(mAccount, Constants.KEY_OC_VERSION);
293 OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, MainApp.getAppContext());
294 mClient = OwnCloudClientManagerFactory.getDefaultSingleton().
295 getClientFor(ocAccount, MainApp.getAppContext());
296 }
297
298 mFile = params[0];
299 final String imageKey = String.valueOf(mFile.getRemoteId());
300
301 // Check disk cache in background thread
302 thumbnail = getBitmapFromDiskCache(imageKey);
303
304 // Not found in disk cache
305 if (thumbnail == null || mFile.needsUpdateThumbnail()) {
306 // Converts dp to pixel
307 Resources r = MainApp.getAppContext().getResources();
308
309 int px = (int) Math.round(r.getDimension(R.dimen.file_icon_size));
310
311 if (mFile.isDown()){
312 Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(
313 mFile.getStoragePath(), px, px);
314
315 if (bitmap != null) {
316 thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
317
318 // Rotate image, obeying exif tag
319 thumbnail = BitmapUtils.rotateImage(thumbnail, mFile.getStoragePath());
320
321 // Add thumbnail to cache
322 addBitmapToCache(imageKey, thumbnail);
323
324 mFile.setNeedsUpdateThumbnail(false);
325 mStorageManager.saveFile(mFile);
326 }
327
328 } else {
329 // Download thumbnail from server
330 if (mClient != null && mServerVersion != null) {
331 OwnCloudVersion serverOCVersion = new OwnCloudVersion(mServerVersion);
332 if (serverOCVersion.compareTo(new OwnCloudVersion(MINOR_SERVER_VERSION_FOR_THUMBS)) >= 0) {
333 try {
334 int status = -1;
335
336 String uri = mClient.getBaseUri() + "/index.php/apps/files/api/v1/thumbnail/" +
337 px + "/" + px + Uri.encode(mFile.getRemotePath(), "/");
338 Log_OC.d("Thumbnail", "URI: " + uri);
339 GetMethod get = new GetMethod(uri);
340 status = mClient.executeMethod(get);
341 if (status == HttpStatus.SC_OK) {
342 byte[] bytes = get.getResponseBody();
343 Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
344 thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
345
346 // Add thumbnail to cache
347 if (thumbnail != null) {
348 addBitmapToCache(imageKey, thumbnail);
349 }
350 }
351 } catch (Exception e) {
352 e.printStackTrace();
353 }
354 } else {
355 Log_OC.d(TAG, "Server too old");
356 }
357 }
358 }
359 }
360
361 } catch (Throwable t) {
362 // the app should never break due to a problem with thumbnails
363 Log_OC.e(TAG, "Generation of thumbnail for " + mFile + " failed", t);
364 if (t instanceof OutOfMemoryError) {
365 System.gc();
366 }
367 }
368
369 return thumbnail;
370 }
371
372 protected void onPostExecute(Bitmap bitmap){
373 if (isCancelled()) {
374 bitmap = null;
375 }
376
377 if (mImageViewReference != null && bitmap != null) {
378 final ImageView imageView = mImageViewReference.get();
379 final ThumbnailGenerationTask bitmapWorkerTask =
380 getBitmapWorkerTask(imageView);
381 if (this == bitmapWorkerTask && imageView != null) {
382 if (imageView.getTag().equals(mFile.getFileId())) {
383 imageView.setImageBitmap(bitmap);
384 }
385 }
386 }
387 }
388 }
389
390
391 public static class AsyncDrawable extends BitmapDrawable {
392 private final WeakReference<ThumbnailGenerationTask> bitmapWorkerTaskReference;
393
394 public AsyncDrawable(
395 Resources res, Bitmap bitmap, ThumbnailGenerationTask bitmapWorkerTask
396 ) {
397
398 super(res, bitmap);
399 bitmapWorkerTaskReference =
400 new WeakReference<ThumbnailGenerationTask>(bitmapWorkerTask);
401 }
402
403 public ThumbnailGenerationTask getBitmapWorkerTask() {
404 return bitmapWorkerTaskReference.get();
405 }
406 }
407
408 public static class AsyncLocalDrawable extends BitmapDrawable {
409 private final WeakReference<ThumbnailLocalGenerationTask> bitmapWorkerLocalTaskReference;
410
411 public AsyncLocalDrawable(Resources res, Bitmap bitmap, ThumbnailLocalGenerationTask bitmapWorkerTask) {
412 super(res, bitmap);
413 bitmapWorkerLocalTaskReference =
414 new WeakReference<ThumbnailLocalGenerationTask>(bitmapWorkerTask);
415 }
416
417 public ThumbnailLocalGenerationTask getBitmapWorkerTask() {
418 return bitmapWorkerLocalTaskReference.get();
419 }
420 }
421
422
423 /**
424 * Remove from cache the remoteId passed
425 * @param fileRemoteId: remote id of mFile passed
426 */
427 public static void removeFileFromCache(String fileRemoteId){
428 synchronized (mThumbnailsDiskCacheLock) {
429 if (mThumbnailCache != null) {
430 mThumbnailCache.removeKey(fileRemoteId);
431 }
432 mThumbnailsDiskCacheLock.notifyAll(); // Wake any waiting threads
433 }
434 }
435
436 }