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
); 
 141     public static boolean cancelPotentialWork(OCFile file
, ImageView imageView
) { 
 142         final ThumbnailGenerationTask bitmapWorkerTask 
= getBitmapWorkerTask(imageView
); 
 144         if (bitmapWorkerTask 
!= null
) { 
 145             final OCFile bitmapData 
= bitmapWorkerTask
.mFile
; 
 146             // If bitmapData is not yet set or it differs from the new data 
 147             if (bitmapData 
== null 
|| bitmapData 
!= file
) { 
 148                 // Cancel previous task 
 149                 bitmapWorkerTask
.cancel(true
); 
 151                 // The same work is already in progress 
 155         // No task associated with the ImageView, or an existing task was cancelled 
 159     public static ThumbnailGenerationTask 
getBitmapWorkerTask(ImageView imageView
) { 
 160         if (imageView 
!= null
) { 
 161             final Drawable drawable 
= imageView
.getDrawable(); 
 162             if (drawable 
instanceof AsyncDrawable
) { 
 163                 final AsyncDrawable asyncDrawable 
= (AsyncDrawable
) drawable
; 
 164                 return asyncDrawable
.getBitmapWorkerTask(); 
 170     public static class ThumbnailGenerationTask 
extends AsyncTask
<OCFile
, Void
, Bitmap
> { 
 171         private final WeakReference
<ImageView
> mImageViewReference
; 
 172         private static Account mAccount
; 
 173         private OCFile mFile
; 
 174         private FileDataStorageManager mStorageManager
; 
 176         public ThumbnailGenerationTask(ImageView imageView
, FileDataStorageManager storageManager
, Account account
) { 
 177          // Use a WeakReference to ensure the ImageView can be garbage collected 
 178             mImageViewReference 
= new WeakReference
<ImageView
>(imageView
); 
 179             if (storageManager 
== null
) 
 180                 throw new IllegalArgumentException("storageManager must not be NULL"); 
 181             mStorageManager 
= storageManager
; 
 185         // Decode image in background. 
 187         protected Bitmap 
doInBackground(OCFile
... params
) { 
 188             Bitmap thumbnail 
= null
; 
 191                 if (mAccount 
!= null
) { 
 192                     AccountManager accountMgr 
= AccountManager
.get(MainApp
.getAppContext()); 
 194                     mServerVersion 
= accountMgr
.getUserData(mAccount
, Constants
.KEY_OC_VERSION
); 
 195                     OwnCloudAccount ocAccount 
= new OwnCloudAccount(mAccount
, MainApp
.getAppContext()); 
 196                     mClient 
= OwnCloudClientManagerFactory
.getDefaultSingleton(). 
 197                             getClientFor(ocAccount
, MainApp
.getAppContext()); 
 201                 final String imageKey 
= String
.valueOf(mFile
.getRemoteId()); 
 203                 // Check disk cache in background thread 
 204                 thumbnail 
= getBitmapFromDiskCache(imageKey
); 
 206                 // Not found in disk cache 
 207                 if (thumbnail 
== null 
|| mFile
.needsUpdateThumbnail()) {  
 208                     // Converts dp to pixel 
 209                     Resources r 
= MainApp
.getAppContext().getResources(); 
 211                     int px 
= (int) Math
.round(r
.getDimension(R
.dimen
.file_icon_size
)); 
 214                         Bitmap bitmap 
= BitmapUtils
.decodeSampledBitmapFromFile( 
 215                                 mFile
.getStoragePath(), px
, px
); 
 217                         if (bitmap 
!= null
) { 
 218                             thumbnail 
= ThumbnailUtils
.extractThumbnail(bitmap
, px
, px
); 
 220                             // Add thumbnail to cache 
 221                             addBitmapToCache(imageKey
, thumbnail
); 
 223                             mFile
.setNeedsUpdateThumbnail(false
); 
 224                             mStorageManager
.saveFile(mFile
); 
 228                         // Download thumbnail from server 
 229                         if (mClient 
!= null 
&& mServerVersion 
!= null
) { 
 230                             OwnCloudVersion serverOCVersion 
= new OwnCloudVersion(mServerVersion
); 
 231                             if (serverOCVersion
.compareTo(new OwnCloudVersion(MINOR_SERVER_VERSION_FOR_THUMBS
)) >= 0) { 
 235                                     String uri 
= mClient
.getBaseUri() + "/index.php/apps/files/api/v1/thumbnail/" +  
 236                                             px 
+ "/" + px 
+ Uri
.encode(mFile
.getRemotePath(), "/"); 
 237                                     Log_OC
.d("Thumbnail", "URI: " + uri
); 
 238                                     GetMethod get 
= new GetMethod(uri
); 
 239                                     status 
= mClient
.executeMethod(get
); 
 240                                     if (status 
== HttpStatus
.SC_OK
) { 
 241                                         byte[] bytes 
= get
.getResponseBody(); 
 242                                         Bitmap bitmap 
= BitmapFactory
.decodeByteArray(bytes
, 0, bytes
.length
); 
 243                                         thumbnail 
= ThumbnailUtils
.extractThumbnail(bitmap
, px
, px
); 
 245                                         // Add thumbnail to cache 
 246                                         if (thumbnail 
!= null
) { 
 247                                             addBitmapToCache(imageKey
, thumbnail
); 
 250                                 } catch (Exception e
) { 
 254                                 Log_OC
.d(TAG
, "Server too old"); 
 260             } catch (Throwable t
) { 
 261                 // the app should never break due to a problem with thumbnails 
 262                 Log_OC
.e(TAG
, "Generation of thumbnail for " + mFile 
+ " failed", t
); 
 263                 if (t 
instanceof OutOfMemoryError
) { 
 271         protected void onPostExecute(Bitmap bitmap
){ 
 276             if (mImageViewReference 
!= null 
&& bitmap 
!= null
) { 
 277                 final ImageView imageView 
= mImageViewReference
.get(); 
 278                 final ThumbnailGenerationTask bitmapWorkerTask 
= 
 279                         getBitmapWorkerTask(imageView
); 
 280                 if (this == bitmapWorkerTask 
&& imageView 
!= null
) { 
 281                     if (imageView
.getTag().equals(mFile
.getFileId())) { 
 282                         imageView
.setImageBitmap(bitmap
); 
 290     public static class AsyncDrawable 
extends BitmapDrawable 
{ 
 291         private final WeakReference
<ThumbnailGenerationTask
> bitmapWorkerTaskReference
; 
 293         public AsyncDrawable( 
 294                 Resources res
, Bitmap bitmap
, ThumbnailGenerationTask bitmapWorkerTask
 
 298             bitmapWorkerTaskReference 
= 
 299                 new WeakReference
<ThumbnailGenerationTask
>(bitmapWorkerTask
); 
 302         public ThumbnailGenerationTask 
getBitmapWorkerTask() { 
 303             return bitmapWorkerTaskReference
.get(); 
 309      * Remove from cache the remoteId passed 
 310      * @param fileRemoteId: remote id of mFile passed 
 312     public static void removeFileFromCache(String fileRemoteId
){ 
 313         synchronized (mThumbnailsDiskCacheLock
) { 
 314             if (mThumbnailCache 
!= null
) { 
 315                 mThumbnailCache
.removeKey(fileRemoteId
); 
 317             mThumbnailsDiskCacheLock
.notifyAll(); // Wake any waiting threads