1 /* ownCloud Android client application
2 * Copyright (C) 2012-2014 ownCloud Inc.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2,
6 * as published by the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 package com
.owncloud
.android
.datamodel
;
21 import java
.lang
.ref
.WeakReference
;
23 import org
.apache
.commons
.httpclient
.HttpStatus
;
24 import org
.apache
.commons
.httpclient
.methods
.GetMethod
;
26 import android
.accounts
.Account
;
27 import android
.accounts
.AccountManager
;
28 import android
.content
.res
.Resources
;
29 import android
.graphics
.Bitmap
;
30 import android
.graphics
.Bitmap
.CompressFormat
;
31 import android
.graphics
.BitmapFactory
;
32 import android
.graphics
.drawable
.BitmapDrawable
;
33 import android
.graphics
.drawable
.Drawable
;
34 import android
.media
.ThumbnailUtils
;
35 import android
.net
.Uri
;
36 import android
.os
.AsyncTask
;
37 import android
.widget
.ImageView
;
39 import com
.owncloud
.android
.MainApp
;
40 import com
.owncloud
.android
.R
;
41 import com
.owncloud
.android
.lib
.common
.OwnCloudAccount
;
42 import com
.owncloud
.android
.lib
.common
.OwnCloudClient
;
43 import com
.owncloud
.android
.lib
.common
.OwnCloudClientManagerFactory
;
44 import com
.owncloud
.android
.lib
.common
.accounts
.AccountUtils
.Constants
;
45 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
;
46 import com
.owncloud
.android
.lib
.resources
.status
.OwnCloudVersion
;
47 import com
.owncloud
.android
.ui
.adapter
.DiskLruImageCache
;
48 import com
.owncloud
.android
.utils
.BitmapUtils
;
49 import com
.owncloud
.android
.utils
.DisplayUtils
;
52 * Manager for concurrent access to thumbnails cache.
54 * @author Tobias Kaminsky
55 * @author David A. Velasco
57 public class ThumbnailsCacheManager
{
59 private static final String TAG
= ThumbnailsCacheManager
.class.getSimpleName();
61 private static final String CACHE_FOLDER
= "thumbnailCache";
62 private static final String MINOR_SERVER_VERSION_FOR_THUMBS
= "7.8.0";
64 private static final Object mThumbnailsDiskCacheLock
= new Object();
65 private static DiskLruImageCache mThumbnailCache
= null
;
66 private static boolean mThumbnailCacheStarting
= true
;
68 private static final int DISK_CACHE_SIZE
= 1024 * 1024 * 10; // 10MB
69 private static final CompressFormat mCompressFormat
= CompressFormat
.JPEG
;
70 private static final int mCompressQuality
= 70;
71 private static OwnCloudClient mClient
= null
;
72 private static String mServerVersion
= null
;
74 public static Bitmap mDefaultImg
=
75 BitmapFactory
.decodeResource(
76 MainApp
.getAppContext().getResources(),
77 DisplayUtils
.getResourceId("image/png", "default.png")
81 public static class InitDiskCacheTask
extends AsyncTask
<File
, Void
, Void
> {
84 protected Void
doInBackground(File
... params
) {
85 synchronized (mThumbnailsDiskCacheLock
) {
86 mThumbnailCacheStarting
= true
;
88 if (mThumbnailCache
== null
) {
90 // Check if media is mounted or storage is built-in, if so,
91 // try and use external cache dir; otherwise use internal cache dir
92 final String cachePath
=
93 MainApp
.getAppContext().getExternalCacheDir().getPath() +
94 File
.separator
+ CACHE_FOLDER
;
95 Log_OC
.d(TAG
, "create dir: " + cachePath
);
96 final File diskCacheDir
= new File(cachePath
);
97 mThumbnailCache
= new DiskLruImageCache(
103 } catch (Exception e
) {
104 Log_OC
.d(TAG
, "Thumbnail cache could not be opened ", e
);
105 mThumbnailCache
= null
;
108 mThumbnailCacheStarting
= false
; // Finished initialization
109 mThumbnailsDiskCacheLock
.notifyAll(); // Wake any waiting threads
116 public static void addBitmapToCache(String key
, Bitmap bitmap
) {
117 synchronized (mThumbnailsDiskCacheLock
) {
118 if (mThumbnailCache
!= null
) {
119 mThumbnailCache
.put(key
, bitmap
);
125 public static Bitmap
getBitmapFromDiskCache(String key
) {
126 synchronized (mThumbnailsDiskCacheLock
) {
127 // Wait while disk cache is started from background thread
128 while (mThumbnailCacheStarting
) {
130 mThumbnailsDiskCacheLock
.wait();
131 } catch (InterruptedException e
) {}
133 if (mThumbnailCache
!= null
) {
134 return (Bitmap
) mThumbnailCache
.getBitmap(key
);
140 public interface AsyncTaskFile
{
141 public String
getId();
142 public String
getTagId();
143 public String
getPath();
144 public String
getRemotePath();
145 public boolean getNeedsUpdateThumbnail();
146 public void setNeedsUpdateThumbnail(boolean needsUpdate
);
147 public boolean getIsDown();
148 public Object
getFile();
151 public static class AsyncTaskFileLocal
implements AsyncTaskFile
{
154 private boolean mNeedsUpdate
;
156 public AsyncTaskFileLocal(File file
){
158 mNeedsUpdate
= false
;
162 public String
getId() {
163 return String
.valueOf(mFile
.hashCode());
167 public String
getTagId() {
168 return String
.valueOf(mFile
.hashCode());
172 public String
getPath() {
173 return mFile
.getAbsolutePath();
177 public String
getRemotePath() {
182 public boolean getNeedsUpdateThumbnail() {
187 public void setNeedsUpdateThumbnail(boolean needsUpdate
) {
188 mNeedsUpdate
= needsUpdate
;
192 public boolean getIsDown() {
197 public Object
getFile() {
202 public static class AsyncTaskOCFile
implements AsyncTaskFile
{
204 private OCFile mFile
;
206 public AsyncTaskOCFile(OCFile file
){
211 public String
getId() {
212 return mFile
.getRemoteId();
216 public String
getTagId() {
217 return String
.valueOf(mFile
.getFileId());
221 public String
getPath() {
222 return mFile
.getStoragePath();
226 public String
getRemotePath() {
227 return mFile
.getRemotePath();
231 public boolean getNeedsUpdateThumbnail() {
232 return mFile
.needsUpdateThumbnail();
236 public void setNeedsUpdateThumbnail(boolean needsUpdate
) {
237 mFile
.setNeedsUpdateThumbnail(needsUpdate
);
241 public boolean getIsDown() {
242 return mFile
.isDown();
246 public Object
getFile() {
251 public static class ThumbnailGenerationGlobalTask
extends AsyncTask
<AsyncTaskFile
, Void
, Bitmap
> {
252 private final WeakReference
<ImageView
> mImageViewReference
;
253 private static Account mAccount
;
254 private AsyncTaskFile mFile
;
255 private FileDataStorageManager mStorageManager
;
258 public ThumbnailGenerationGlobalTask(ImageView imageView
, FileDataStorageManager storageManager
, Account account
) {
259 // Use a WeakReference to ensure the ImageView can be garbage collected
260 mImageViewReference
= new WeakReference
<ImageView
>(imageView
);
261 if (storageManager
== null
)
262 throw new IllegalArgumentException("storageManager must not be NULL");
263 mStorageManager
= storageManager
;
267 public ThumbnailGenerationGlobalTask(ImageView imageView
) {
268 // Use a WeakReference to ensure the ImageView can be garbage collected
269 mImageViewReference
= new WeakReference
<ImageView
>(imageView
);
273 protected Bitmap
doInBackground(AsyncTaskFile
... params
) {
274 Bitmap thumbnail
= null
;
277 if (mAccount
!= null
) {
278 AccountManager accountMgr
= AccountManager
.get(MainApp
.getAppContext());
280 mServerVersion
= accountMgr
.getUserData(mAccount
, Constants
.KEY_OC_VERSION
);
281 OwnCloudAccount ocAccount
= new OwnCloudAccount(mAccount
, MainApp
.getAppContext());
282 mClient
= OwnCloudClientManagerFactory
.getDefaultSingleton().
283 getClientFor(ocAccount
, MainApp
.getAppContext());
288 final String imageKey
= String
.valueOf(mFile
.getId());
290 // Check disk cache in background thread
291 thumbnail
= getBitmapFromDiskCache(imageKey
);
293 // Check if mFile passed is an OCFile (for OCFile thumbs)
294 if (mFile
instanceof AsyncTaskOCFile
) {
296 // Not found in disk cache
297 if (thumbnail
== null
|| mFile
.getNeedsUpdateThumbnail()) {
299 int px
= getThumbnailDimension();
301 if (mFile
.getIsDown()) {
302 Bitmap bitmap
= BitmapUtils
.decodeSampledBitmapFromFile(
303 mFile
.getPath(), px
, px
);
305 if (bitmap
!= null
) {
306 thumbnail
= addThumbnailToCache(imageKey
, bitmap
, mFile
.getPath(), px
);
308 mFile
.setNeedsUpdateThumbnail(false
);
309 mStorageManager
.saveFile((OCFile
) mFile
.getFile());
313 // Download thumbnail from server
314 if (mClient
!= null
&& mServerVersion
!= null
) {
315 OwnCloudVersion serverOCVersion
= new OwnCloudVersion(mServerVersion
);
316 if (serverOCVersion
.compareTo(new OwnCloudVersion(MINOR_SERVER_VERSION_FOR_THUMBS
)) >= 0) {
320 String uri
= mClient
.getBaseUri() + "/index.php/apps/files/api/v1/thumbnail/" +
321 px
+ "/" + px
+ Uri
.encode(mFile
.getRemotePath(), "/");
322 Log_OC
.d("Thumbnail", "URI: " + uri
);
323 GetMethod get
= new GetMethod(uri
);
324 status
= mClient
.executeMethod(get
);
325 if (status
== HttpStatus
.SC_OK
) {
326 byte[] bytes
= get
.getResponseBody();
327 Bitmap bitmap
= BitmapFactory
.decodeByteArray(bytes
, 0, bytes
.length
);
328 thumbnail
= ThumbnailUtils
.extractThumbnail(bitmap
, px
, px
);
330 // Add thumbnail to cache
331 if (thumbnail
!= null
) {
332 addBitmapToCache(imageKey
, thumbnail
);
335 } catch (Exception e
) {
339 Log_OC
.d(TAG
, "Server too old");
344 // Check if mFile passed is a File (for local thumbs)
345 } else if (mFile
instanceof AsyncTaskFileLocal
) {
347 // Not found in disk cache
348 if (thumbnail
== null
) {
350 int px
= getThumbnailDimension();
352 Bitmap bitmap
= BitmapUtils
.decodeSampledBitmapFromFile(
353 mFile
.getPath(), px
, px
);
355 if (bitmap
!= null
) {
356 thumbnail
= addThumbnailToCache(imageKey
, bitmap
, mFile
.getPath(), px
);
363 // the app should never break due to a problem with thumbnails
364 Log_OC
.e(TAG
, "Generation of thumbnail for " + mFile
+ " failed", t
);
365 if (t
instanceof OutOfMemoryError
) {
374 protected void onPostExecute(Bitmap bitmap
){
379 if (mImageViewReference
!= null
&& bitmap
!= null
) {
380 final ImageView imageView
= mImageViewReference
.get();
381 final ThumbnailGenerationGlobalTask bitmapWorkerTask
= getBitmapGlobalWorkerTask(imageView
);
382 if (this == bitmapWorkerTask
&& imageView
!= null
) {
383 if (String
.valueOf(imageView
.getTag()).equals(mFile
.getTagId())) {
384 imageView
.setImageBitmap(bitmap
);
391 * Add thumbnail to cache
392 * @param imageKey: thumb key
393 * @param bitmap: image for extracting thumbnail
394 * @param path: image path
395 * @param px: thumbnail dp
398 private Bitmap
addThumbnailToCache(String imageKey
, Bitmap bitmap
, String path
, int px
){
400 Bitmap thumbnail
= ThumbnailUtils
.extractThumbnail(bitmap
, px
, px
);
402 // Rotate image, obeying exif tag
403 thumbnail
= BitmapUtils
.rotateImage(thumbnail
,path
);
405 // Add thumbnail to cache
406 addBitmapToCache(imageKey
, thumbnail
);
412 * Converts size of file icon from dp to pixel
415 private int getThumbnailDimension(){
416 // Converts dp to pixel
417 Resources r
= MainApp
.getAppContext().getResources();
418 return (int) Math
.round(r
.getDimension(R
.dimen
.file_icon_size
));
422 public static boolean cancelPotentialGlobalWork(AsyncTaskFile file
, ImageView imageView
) {
423 final ThumbnailGenerationGlobalTask bitmapWorkerTask
= getBitmapGlobalWorkerTask(imageView
);
425 if (bitmapWorkerTask
!= null
) {
426 final AsyncTaskFile bitmapData
= bitmapWorkerTask
.mFile
;
427 // If bitmapData is not yet set or it differs from the new data
428 if (bitmapData
== null
|| bitmapData
!= file
) {
429 // Cancel previous task
430 bitmapWorkerTask
.cancel(true
);
432 // The same work is already in progress
436 // No task associated with the ImageView, or an existing task was cancelled
440 public static ThumbnailGenerationGlobalTask
getBitmapGlobalWorkerTask(ImageView imageView
) {
441 if (imageView
!= null
) {
442 final Drawable drawable
= imageView
.getDrawable();
443 if (drawable
instanceof AsyncGlobalDrawable
) {
444 final AsyncGlobalDrawable asyncDrawable
= (AsyncGlobalDrawable
) drawable
;
445 return asyncDrawable
.getBitmapWorkerTask();
451 public static class AsyncGlobalDrawable
extends BitmapDrawable
{
452 private final WeakReference
<ThumbnailGenerationGlobalTask
> bitmapWorkerTaskReference
;
454 public AsyncGlobalDrawable(
455 Resources res
, Bitmap bitmap
, ThumbnailGenerationGlobalTask bitmapWorkerTask
459 bitmapWorkerTaskReference
=
460 new WeakReference
<ThumbnailGenerationGlobalTask
>(bitmapWorkerTask
);
463 public ThumbnailGenerationGlobalTask
getBitmapWorkerTask() {
464 return bitmapWorkerTaskReference
.get();