2 * ownCloud Android client application
4 * @author Tobias Kaminsky
5 * @author David A. Velasco
6 * Copyright (C) 2015 ownCloud Inc.
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.
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.
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/>.
22 package com
.owncloud
.android
.datamodel
;
25 import java
.io
.InputStream
;
26 import java
.lang
.ref
.WeakReference
;
28 import org
.apache
.commons
.httpclient
.HttpStatus
;
29 import org
.apache
.commons
.httpclient
.methods
.GetMethod
;
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
;
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
;
60 * Manager for concurrent access to thumbnails cache.
62 public class ThumbnailsCacheManager
{
64 private static final String TAG
= ThumbnailsCacheManager
.class.getSimpleName();
66 private static final String CACHE_FOLDER
= "thumbnailCache";
67 private static final Integer CACHE_SIZE_MB
= 10;
69 private static final Object mThumbnailsDiskCacheLock
= new Object();
70 private static DiskLruImageCache mThumbnailCache
= null
;
71 private static boolean mThumbnailCacheStarting
= true
;
73 private static final CompressFormat mCompressFormat
= CompressFormat
.JPEG
;
74 private static final int mCompressQuality
= 70;
75 private static OwnCloudClient mClient
= null
;
77 public static Bitmap mDefaultImg
=
78 BitmapFactory
.decodeResource(
79 MainApp
.getAppContext().getResources(),
84 public static class InitDiskCacheTask
extends AsyncTask
<File
, Void
, Void
> {
87 protected Void
doInBackground(File
... params
) {
88 synchronized (mThumbnailsDiskCacheLock
) {
89 mThumbnailCacheStarting
= true
;
91 if (mThumbnailCache
== null
) {
93 SharedPreferences appPrefs
=
94 PreferenceManager
.getDefaultSharedPreferences(MainApp
.getAppContext());
95 // due to backward compatibility
96 Integer cacheSize
= CACHE_SIZE_MB
* 1024 * 1024;
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;
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(
118 } catch (Exception e
) {
119 Log_OC
.d(TAG
, "Thumbnail cache could not be opened ", e
);
120 mThumbnailCache
= null
;
123 mThumbnailCacheStarting
= false
; // Finished initialization
124 mThumbnailsDiskCacheLock
.notifyAll(); // Wake any waiting threads
131 public static void addBitmapToCache(String key
, Bitmap bitmap
) {
132 synchronized (mThumbnailsDiskCacheLock
) {
133 if (mThumbnailCache
!= null
) {
134 mThumbnailCache
.put(key
, bitmap
);
140 public static Bitmap
getBitmapFromDiskCache(String key
) {
141 synchronized (mThumbnailsDiskCacheLock
) {
142 // Wait while disk cache is started from background thread
143 while (mThumbnailCacheStarting
) {
145 mThumbnailsDiskCacheLock
.wait();
146 } catch (InterruptedException e
) {
147 Log_OC
.e(TAG
, "Wait in mThumbnailsDiskCacheLock was interrupted", e
);
150 if (mThumbnailCache
!= null
) {
151 return mThumbnailCache
.getBitmap(key
);
158 * Sets max size of cache
159 * @param maxSize in MB
162 public static boolean setMaxSize(long maxSize
){
163 if (mThumbnailCache
!= null
){
164 mThumbnailCache
.setMaxSize(maxSize
* 1024 * 1024);
172 * Shows max cache size
173 * @return max cache size in MB.
175 public static long getMaxSize(){
176 if (mThumbnailCache
== null
) {
177 new ThumbnailsCacheManager
.InitDiskCacheTask().execute();
179 return mThumbnailCache
.getMaxSize() / 1024 / 1024;
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
;
189 public ThumbnailGenerationTask(ImageView imageView
, FileDataStorageManager storageManager
,
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
;
199 public ThumbnailGenerationTask(ImageView imageView
) {
200 // Use a WeakReference to ensure the ImageView can be garbage collected
201 mImageViewReference
= new WeakReference
<ImageView
>(imageView
);
205 protected Bitmap
doInBackground(Object
... params
) {
206 Bitmap thumbnail
= null
;
209 if (mAccount
!= null
) {
210 OwnCloudAccount ocAccount
= new OwnCloudAccount(mAccount
,
211 MainApp
.getAppContext());
212 mClient
= OwnCloudClientManagerFactory
.getDefaultSingleton().
213 getClientFor(ocAccount
, MainApp
.getAppContext());
218 if (mFile
instanceof OCFile
) {
219 thumbnail
= doOCFileInBackground();
220 } else if (mFile
instanceof File
) {
221 thumbnail
= doFileInBackground();
222 //} else { do nothing
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
) {
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
) {
242 if (mFile
instanceof OCFile
){
243 tagId
= String
.valueOf(((OCFile
)mFile
).getFileId());
244 } else if (mFile
instanceof File
){
245 tagId
= String
.valueOf(mFile
.hashCode());
247 if (String
.valueOf(imageView
.getTag()).equals(tagId
)) {
248 imageView
.setImageBitmap(bitmap
);
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
262 private Bitmap
addThumbnailToCache(String imageKey
, Bitmap bitmap
, String path
, int px
){
264 Bitmap thumbnail
= ThumbnailUtils
.extractThumbnail(bitmap
, px
, px
);
266 // Rotate image, obeying exif tag
267 thumbnail
= BitmapUtils
.rotateImage(thumbnail
,path
);
269 // Add thumbnail to cache
270 addBitmapToCache(imageKey
, thumbnail
);
276 * Converts size of file icon from dp to pixel
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
));
285 private Bitmap
doOCFileInBackground() {
286 OCFile file
= (OCFile
)mFile
;
288 final String imageKey
= String
.valueOf(file
.getRemoteId());
290 // Check disk cache in background thread
291 Bitmap thumbnail
= getBitmapFromDiskCache(imageKey
);
293 // Not found in disk cache
294 if (thumbnail
== null
|| file
.needsUpdateThumbnail()) {
296 int px
= getThumbnailDimension();
299 Bitmap temp
= BitmapUtils
.decodeSampledBitmapFromFile(
300 file
.getStoragePath(), px
, px
);
301 Bitmap bitmap
= ThumbnailUtils
.extractThumbnail(temp
, px
, px
);
303 if (bitmap
!= null
) {
305 if (file
.getMimetype().equalsIgnoreCase("image/png")) {
306 bitmap
= handlePNG(bitmap
, px
);
309 thumbnail
= addThumbnailToCache(imageKey
, bitmap
, file
.getStoragePath(), px
);
311 file
.setNeedsUpdateThumbnail(false
);
312 mStorageManager
.saveFile(file
);
316 // Download thumbnail from server
317 OwnCloudVersion serverOCVersion
= AccountUtils
.getServerVersion(mAccount
);
318 if (mClient
!= null
&& serverOCVersion
!= null
) {
319 if (serverOCVersion
.supportsRemoteThumbnails()) {
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
);
333 if (file
.getMimetype().equalsIgnoreCase("image/png")) {
334 thumbnail
= handlePNG(thumbnail
, px
);
337 // Add thumbnail to cache
338 if (thumbnail
!= null
) {
339 addBitmapToCache(imageKey
, thumbnail
);
342 } catch (Exception e
) {
346 Log_OC
.d(TAG
, "Server too old");
356 private Bitmap
handlePNG(Bitmap bitmap
, int px
){
357 Bitmap resultBitmap
= Bitmap
.createBitmap(px
,
359 Bitmap
.Config
.ARGB_8888
);
360 Canvas c
= new Canvas(resultBitmap
);
362 c
.drawColor(MainApp
.getAppContext().getResources().
363 getColor(R
.color
.background_color
));
364 c
.drawBitmap(bitmap
, 0, 0, null
);
369 private Bitmap
doFileInBackground() {
370 File file
= (File
)mFile
;
372 final String imageKey
= String
.valueOf(file
.hashCode());
374 // Check disk cache in background thread
375 Bitmap thumbnail
= getBitmapFromDiskCache(imageKey
);
377 // Not found in disk cache
378 if (thumbnail
== null
) {
380 int px
= getThumbnailDimension();
382 Bitmap bitmap
= BitmapUtils
.decodeSampledBitmapFromFile(
383 file
.getAbsolutePath(), px
, px
);
385 if (bitmap
!= null
) {
386 thumbnail
= addThumbnailToCache(imageKey
, bitmap
, file
.getPath(), px
);
394 public static boolean cancelPotentialWork(Object file
, ImageView imageView
) {
395 final ThumbnailGenerationTask bitmapWorkerTask
= getBitmapWorkerTask(imageView
);
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");
405 // The same work is already in progress
409 // No task associated with the ImageView, or an existing task was cancelled
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();
424 public static class AsyncDrawable
extends BitmapDrawable
{
425 private final WeakReference
<ThumbnailGenerationTask
> bitmapWorkerTaskReference
;
427 public AsyncDrawable(
428 Resources res
, Bitmap bitmap
, ThumbnailGenerationTask bitmapWorkerTask
432 bitmapWorkerTaskReference
=
433 new WeakReference
<ThumbnailGenerationTask
>(bitmapWorkerTask
);
436 public ThumbnailGenerationTask
getBitmapWorkerTask() {
437 return bitmapWorkerTaskReference
.get();