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