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