resized image is loaded instead of full sized image.
[pub/Android/ownCloud.git] / src / com / owncloud / android / datamodel / ThumbnailsCacheManager.java
1 /**
2 * ownCloud Android client application
3 *
4 * @author Tobias Kaminsky
5 * @author David A. Velasco
6 * Copyright (C) 2015 ownCloud Inc.
7 *
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2,
10 * as published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 *
20 */
21
22 package com.owncloud.android.datamodel;
23
24 import java.io.File;
25 import java.lang.ref.WeakReference;
26
27 import org.apache.commons.httpclient.HttpStatus;
28 import org.apache.commons.httpclient.methods.GetMethod;
29
30 import android.accounts.Account;
31 import android.accounts.AccountManager;
32 import android.content.Context;
33 import android.content.res.Resources;
34 import android.graphics.Bitmap;
35 import android.graphics.Bitmap.CompressFormat;
36 import android.graphics.BitmapFactory;
37 import android.graphics.Point;
38 import android.graphics.drawable.BitmapDrawable;
39 import android.graphics.drawable.Drawable;
40 import android.media.ThumbnailUtils;
41 import android.net.Uri;
42 import android.os.AsyncTask;
43 import android.view.Display;
44 import android.view.View;
45 import android.view.WindowManager;
46 import android.widget.ImageView;
47 import android.widget.ProgressBar;
48
49 import com.owncloud.android.MainApp;
50 import com.owncloud.android.R;
51 import com.owncloud.android.lib.common.OwnCloudAccount;
52 import com.owncloud.android.lib.common.OwnCloudClient;
53 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
54 import com.owncloud.android.lib.common.accounts.AccountUtils.Constants;
55 import com.owncloud.android.lib.common.utils.Log_OC;
56 import com.owncloud.android.lib.resources.status.OwnCloudVersion;
57 import com.owncloud.android.ui.adapter.DiskLruImageCache;
58 import com.owncloud.android.utils.BitmapUtils;
59 import com.owncloud.android.utils.DisplayUtils;
60
61 /**
62 * Manager for concurrent access to thumbnails cache.
63 */
64 public class ThumbnailsCacheManager {
65
66 private static final String TAG = ThumbnailsCacheManager.class.getSimpleName();
67
68 private static final String CACHE_FOLDER = "thumbnailCache";
69 private static final String MINOR_SERVER_VERSION_FOR_THUMBS = "7.8.0";
70
71 private static final Object mThumbnailsDiskCacheLock = new Object();
72 private static DiskLruImageCache mThumbnailCache = null;
73 private static boolean mThumbnailCacheStarting = true;
74
75 private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
76 private static final CompressFormat mCompressFormat = CompressFormat.JPEG;
77 private static final int mCompressQuality = 70;
78 private static OwnCloudClient mClient = null;
79 private static String mServerVersion = null;
80
81 public static Bitmap mDefaultImg =
82 BitmapFactory.decodeResource(
83 MainApp.getAppContext().getResources(),
84 DisplayUtils.getFileTypeIconId("image/png", "default.png")
85 );
86
87
88 public static class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
89
90 @Override
91 protected Void doInBackground(File... params) {
92 synchronized (mThumbnailsDiskCacheLock) {
93 mThumbnailCacheStarting = true;
94
95 if (mThumbnailCache == null) {
96 try {
97 // Check if media is mounted or storage is built-in, if so,
98 // try and use external cache dir; otherwise use internal cache dir
99 final String cachePath =
100 MainApp.getAppContext().getExternalCacheDir().getPath() +
101 File.separator + CACHE_FOLDER;
102 Log_OC.d(TAG, "create dir: " + cachePath);
103 final File diskCacheDir = new File(cachePath);
104 mThumbnailCache = new DiskLruImageCache(
105 diskCacheDir,
106 DISK_CACHE_SIZE,
107 mCompressFormat,
108 mCompressQuality
109 );
110 } catch (Exception e) {
111 Log_OC.d(TAG, "Thumbnail cache could not be opened ", e);
112 mThumbnailCache = null;
113 }
114 }
115 mThumbnailCacheStarting = false; // Finished initialization
116 mThumbnailsDiskCacheLock.notifyAll(); // Wake any waiting threads
117 }
118 return null;
119 }
120 }
121
122
123 public static void addBitmapToCache(String key, Bitmap bitmap) {
124 synchronized (mThumbnailsDiskCacheLock) {
125 if (mThumbnailCache != null) {
126 mThumbnailCache.put(key, bitmap);
127 }
128 }
129 }
130
131
132 public static Bitmap getBitmapFromDiskCache(String key) {
133 synchronized (mThumbnailsDiskCacheLock) {
134 // Wait while disk cache is started from background thread
135 while (mThumbnailCacheStarting) {
136 try {
137 mThumbnailsDiskCacheLock.wait();
138 } catch (InterruptedException e) {}
139 }
140 if (mThumbnailCache != null) {
141 return (Bitmap) mThumbnailCache.getBitmap(key);
142 }
143 }
144 return null;
145 }
146
147 public static class ThumbnailGenerationTask extends AsyncTask<Object, Void, Bitmap> {
148 private final WeakReference<ImageView> mImageViewReference;
149 private WeakReference<ProgressBar> mProgressWheelRef;
150 private static Account mAccount;
151 private Object mFile;
152 private Boolean mIsThumbnail;
153 private FileDataStorageManager mStorageManager;
154
155 public ThumbnailGenerationTask(ImageView imageView, FileDataStorageManager storageManager,
156 Account account) {
157 // Use a WeakReference to ensure the ImageView can be garbage collected
158 mImageViewReference = new WeakReference<ImageView>(imageView);
159 if (storageManager == null)
160 throw new IllegalArgumentException("storageManager must not be NULL");
161 mStorageManager = storageManager;
162 mAccount = account;
163 }
164
165 public ThumbnailGenerationTask(ImageView imageView, FileDataStorageManager storageManager,
166 Account account, ProgressBar progressWheel) {
167 this(imageView, storageManager, account);
168 mProgressWheelRef = new WeakReference<ProgressBar>(progressWheel);
169 }
170
171 public ThumbnailGenerationTask(ImageView imageView) {
172 // Use a WeakReference to ensure the ImageView can be garbage collected
173 mImageViewReference = new WeakReference<ImageView>(imageView);
174 }
175
176 @Override
177 protected Bitmap doInBackground(Object... params) {
178 Bitmap thumbnail = null;
179
180 try {
181 if (mAccount != null) {
182 AccountManager accountMgr = AccountManager.get(MainApp.getAppContext());
183
184 mServerVersion = accountMgr.getUserData(mAccount, Constants.KEY_OC_VERSION);
185 OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount,
186 MainApp.getAppContext());
187 mClient = OwnCloudClientManagerFactory.getDefaultSingleton().
188 getClientFor(ocAccount, MainApp.getAppContext());
189 }
190
191 mFile = params[0];
192 mIsThumbnail = (Boolean) params[1];
193
194
195 if (mFile instanceof OCFile) {
196 thumbnail = doOCFileInBackground(mIsThumbnail);
197 } else if (mFile instanceof File) {
198 thumbnail = doFileInBackground(mIsThumbnail);
199 } else {
200 // do nothing
201 }
202
203 }catch(Throwable t){
204 // the app should never break due to a problem with thumbnails
205 Log_OC.e(TAG, "Generation of thumbnail for " + mFile + " failed", t);
206 if (t instanceof OutOfMemoryError) {
207 System.gc();
208 }
209 }
210
211 return thumbnail;
212 }
213
214 protected void onPostExecute(Bitmap bitmap){
215 if (isCancelled()) {
216 bitmap = null;
217 }
218
219 if (mImageViewReference != null && bitmap != null) {
220 final ImageView imageView = mImageViewReference.get();
221 final ThumbnailGenerationTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
222 if (this == bitmapWorkerTask && imageView != null) {
223 String tagId = "";
224 if (mFile instanceof OCFile){
225 tagId = String.valueOf(((OCFile)mFile).getFileId());
226 } else if (mFile instanceof File){
227 tagId = String.valueOf(((File)mFile).hashCode());
228 }
229 if (String.valueOf(imageView.getTag()).equals(tagId)) {
230 if (mProgressWheelRef != null) {
231 final ProgressBar progressWheel = mProgressWheelRef.get();
232 if (progressWheel != null) {
233 progressWheel.setVisibility(View.GONE);
234 }
235 }
236 imageView.setImageBitmap(bitmap);
237 imageView.setVisibility(View.VISIBLE);
238 }
239 }
240 }
241 }
242
243 /**
244 * Add thumbnail to cache
245 * @param imageKey: thumb key
246 * @param bitmap: image for extracting thumbnail
247 * @param path: image path
248 * @param pxW: thumbnail width
249 * @param pxH: thumbnail height
250 * @return Bitmap
251 */
252 private Bitmap addThumbnailToCache(String imageKey, Bitmap bitmap, String path, int pxW, int pxH){
253
254 Bitmap thumbnail = ThumbnailUtils.extractThumbnail(bitmap, pxW, pxH);
255
256 // Rotate image, obeying exif tag
257 thumbnail = BitmapUtils.rotateImage(thumbnail,path);
258
259 // Add thumbnail to cache
260 addBitmapToCache(imageKey, thumbnail);
261
262 return thumbnail;
263 }
264
265 /**
266 * Converts size of file icon from dp to pixel
267 * @return int
268 */
269 private int getThumbnailDimension(){
270 // Converts dp to pixel
271 Resources r = MainApp.getAppContext().getResources();
272 return (int) Math.round(r.getDimension(R.dimen.file_icon_size_grid));
273 }
274
275 private Point getScreenDimension(){
276 WindowManager wm = (WindowManager) MainApp.getAppContext().getSystemService(Context.WINDOW_SERVICE);
277 Display display = wm.getDefaultDisplay();
278 Point test = new Point();
279 display.getSize(test);
280 return test;
281 }
282
283 private Bitmap doOCFileInBackground(Boolean isThumbnail) {
284 Bitmap thumbnail = null;
285 OCFile file = (OCFile)mFile;
286
287 // distinguish between thumbnail and resized image
288 String temp = String.valueOf(file.getRemoteId());
289 if (isThumbnail){
290 temp = "t" + temp;
291 } else {
292 temp = "r" + temp;
293 }
294
295 final String imageKey = temp;
296
297 // Check disk cache in background thread
298 thumbnail = getBitmapFromDiskCache(imageKey);
299
300 // Not found in disk cache
301 if (thumbnail == null || file.needsUpdateThumbnail()) {
302 int pxW = 0;
303 int pxH = 0;
304 if (mIsThumbnail) {
305 pxW = pxH = getThumbnailDimension();
306 } else {
307 Point p = getScreenDimension();
308 pxW = p.x;
309 pxH = p.y;
310 }
311
312 if (file.isDown()) {
313 Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(
314 file.getStoragePath(), pxW, pxH);
315
316 if (bitmap != null) {
317 thumbnail = addThumbnailToCache(imageKey, bitmap, file.getStoragePath(), pxW, pxH);
318
319 file.setNeedsUpdateThumbnail(false);
320 mStorageManager.saveFile(file);
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(
328 new OwnCloudVersion(MINOR_SERVER_VERSION_FOR_THUMBS)) >= 0) {
329 try {
330 int status = -1;
331
332 String uri = mClient.getBaseUri() + "" +
333 "/index.php/apps/files/api/v1/thumbnail/" +
334 pxW + "/" + pxH + Uri.encode(file.getRemotePath(), "/");
335 Log_OC.d("Thumbnail", "URI: " + uri);
336 GetMethod get = new GetMethod(uri);
337 status = mClient.executeMethod(get);
338 if (status == HttpStatus.SC_OK) {
339 byte[] bytes = get.getResponseBody();
340
341 String type = "";
342 if (mIsThumbnail){
343 type = "Thumbnail";
344 } else {
345 type = "Resized image";
346 }
347 Log_OC.d("Thumbnail", type + " size of " + file.getRemotePath() + ": " + bytes.length);
348
349 Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0,
350 bytes.length);
351
352 if (mIsThumbnail) {
353 thumbnail = ThumbnailUtils.extractThumbnail(bitmap, pxW, pxH);
354 } else {
355 thumbnail = bitmap;
356 }
357
358 // Add thumbnail to cache
359 if (thumbnail != null) {
360 addBitmapToCache(imageKey, thumbnail);
361 }
362 }
363 } catch (Exception e) {
364 e.printStackTrace();
365 }
366 } else {
367 Log_OC.d(TAG, "Server too old");
368 }
369 }
370 }
371 }
372
373 return thumbnail;
374
375 }
376
377 private Bitmap doFileInBackground(Boolean mIsThumbnail) {
378 Bitmap thumbnail = null;
379 File file = (File)mFile;
380
381 final String imageKey = String.valueOf(file.hashCode());
382
383 // Check disk cache in background thread
384 thumbnail = getBitmapFromDiskCache(imageKey);
385
386 // Not found in disk cache
387 if (thumbnail == null) {
388 int pxW = 0;
389 int pxH = 0;
390 if (mIsThumbnail) {
391 pxW = pxH = getThumbnailDimension();
392 } else {
393 Point p = getScreenDimension();
394 pxW = p.x;
395 pxH = p.y;
396 }
397
398 Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(
399 file.getAbsolutePath(), pxW, pxH);
400
401 if (bitmap != null) {
402 thumbnail = addThumbnailToCache(imageKey, bitmap, file.getPath(), pxW, pxH);
403 }
404 }
405 return thumbnail;
406 }
407
408 }
409
410 public static boolean cancelPotentialWork(Object file, ImageView imageView) {
411 final ThumbnailGenerationTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
412
413 if (bitmapWorkerTask != null) {
414 final Object bitmapData = bitmapWorkerTask.mFile;
415 // If bitmapData is not yet set or it differs from the new data
416 if (bitmapData == null || bitmapData != file) {
417 // Cancel previous task
418 bitmapWorkerTask.cancel(true);
419 } else {
420 // The same work is already in progress
421 return false;
422 }
423 }
424 // No task associated with the ImageView, or an existing task was cancelled
425 return true;
426 }
427
428 public static ThumbnailGenerationTask getBitmapWorkerTask(ImageView imageView) {
429 if (imageView != null) {
430 final Drawable drawable = imageView.getDrawable();
431 if (drawable instanceof AsyncDrawable) {
432 final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
433 return asyncDrawable.getBitmapWorkerTask();
434 }
435 }
436 return null;
437 }
438
439 public static class AsyncDrawable extends BitmapDrawable {
440 private final WeakReference<ThumbnailGenerationTask> bitmapWorkerTaskReference;
441
442 public AsyncDrawable(
443 Resources res, Bitmap bitmap, ThumbnailGenerationTask bitmapWorkerTask
444 ) {
445
446 super(res, bitmap);
447 bitmapWorkerTaskReference =
448 new WeakReference<ThumbnailGenerationTask>(bitmapWorkerTask);
449 }
450
451 public ThumbnailGenerationTask getBitmapWorkerTask() {
452 return bitmapWorkerTaskReference.get();
453 }
454 }
455 }