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