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