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
.accounts
.AccountManager
; 
  33 import android
.content
.Context
; 
  34 import android
.content
.res
.Resources
; 
  35 import android
.graphics
.Bitmap
; 
  36 import android
.graphics
.Bitmap
.CompressFormat
; 
  37 import android
.graphics
.BitmapFactory
; 
  38 import android
.graphics
.Point
; 
  39 import android
.graphics
.drawable
.BitmapDrawable
; 
  40 import android
.graphics
.drawable
.Drawable
; 
  41 import android
.media
.ThumbnailUtils
; 
  42 import android
.net
.Uri
; 
  43 import android
.os
.AsyncTask
; 
  44 import android
.view
.Display
; 
  45 import android
.view
.View
; 
  46 import android
.view
.WindowManager
; 
  47 import android
.widget
.ImageView
; 
  48 import android
.widget
.ProgressBar
; 
  50 import com
.owncloud
.android
.MainApp
; 
  51 import com
.owncloud
.android
.R
; 
  52 import com
.owncloud
.android
.authentication
.AccountUtils
; 
  53 import com
.owncloud
.android
.lib
.common
.OwnCloudAccount
; 
  54 import com
.owncloud
.android
.lib
.common
.OwnCloudClient
; 
  55 import com
.owncloud
.android
.lib
.common
.OwnCloudClientManagerFactory
; 
  56 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
; 
  57 import com
.owncloud
.android
.lib
.resources
.status
.OwnCloudVersion
; 
  58 import com
.owncloud
.android
.ui
.adapter
.DiskLruImageCache
; 
  59 import com
.owncloud
.android
.utils
.BitmapUtils
; 
  60 import com
.owncloud
.android
.utils
.DisplayUtils
; 
  63  * Manager for concurrent access to thumbnails cache. 
  65 public class ThumbnailsCacheManager 
{ 
  67     private static final String TAG 
= ThumbnailsCacheManager
.class.getSimpleName(); 
  69     private static final String CACHE_FOLDER 
= "thumbnailCache"; 
  71     private static final Object mThumbnailsDiskCacheLock 
= new Object(); 
  72     private static DiskLruImageCache mThumbnailCache 
= null
; 
  73     private static boolean mThumbnailCacheStarting 
= true
; 
  75     private static final int DISK_CACHE_SIZE 
= 1024 * 1024 * 10; // 10MB 
  76     private static final CompressFormat mCompressFormat 
= CompressFormat
.JPEG
; 
  77     private static final int mCompressQuality 
= 70; 
  78     private static OwnCloudClient mClient 
= null
; 
  80     public static Bitmap mDefaultImg 
=  
  81             BitmapFactory
.decodeResource( 
  82                     MainApp
.getAppContext().getResources(),  
  83                     DisplayUtils
.getFileTypeIconId("image/png", "default.png") 
  87     public static class InitDiskCacheTask 
extends AsyncTask
<File
, Void
, Void
> { 
  90         protected Void 
doInBackground(File
... params
) { 
  91             synchronized (mThumbnailsDiskCacheLock
) { 
  92                 mThumbnailCacheStarting 
= true
; 
  94                 if (mThumbnailCache 
== null
) { 
  96                         // Check if media is mounted or storage is built-in, if so,  
  97                         // try and use external cache dir; otherwise use internal cache dir 
  98                         final String cachePath 
=  
  99                                 MainApp
.getAppContext().getExternalCacheDir().getPath() +  
 100                                 File
.separator 
+ CACHE_FOLDER
; 
 101                         Log_OC
.d(TAG
, "create dir: " + cachePath
); 
 102                         final File diskCacheDir 
= new File(cachePath
); 
 103                         mThumbnailCache 
= new DiskLruImageCache( 
 109                     } catch (Exception e
) { 
 110                         Log_OC
.d(TAG
, "Thumbnail cache could not be opened ", e
); 
 111                         mThumbnailCache 
= null
; 
 114                 mThumbnailCacheStarting 
= false
; // Finished initialization 
 115                 mThumbnailsDiskCacheLock
.notifyAll(); // Wake any waiting threads 
 122     public static void addBitmapToCache(String key
, Bitmap bitmap
) { 
 123         synchronized (mThumbnailsDiskCacheLock
) { 
 124             if (mThumbnailCache 
!= null
) { 
 125                 mThumbnailCache
.put(key
, bitmap
); 
 131     public static Bitmap 
getBitmapFromDiskCache(String key
) { 
 132         synchronized (mThumbnailsDiskCacheLock
) { 
 133             // Wait while disk cache is started from background thread 
 134             while (mThumbnailCacheStarting
) { 
 136                     mThumbnailsDiskCacheLock
.wait(); 
 137                 } catch (InterruptedException e
) { 
 138                     Log_OC
.e(TAG
, "Wait in mThumbnailsDiskCacheLock was interrupted", e
); 
 141             if (mThumbnailCache 
!= null
) { 
 142                 return mThumbnailCache
.getBitmap(key
); 
 148     public static class ThumbnailGenerationTask 
extends AsyncTask
<Object
, Void
, Bitmap
> { 
 149         private final WeakReference
<ImageView
> mImageViewReference
; 
 150         private WeakReference
<ProgressBar
> mProgressWheelRef
; 
 151         private static Account mAccount
; 
 152         private Object mFile
; 
 153         private Boolean mIsThumbnail
; 
 154         private FileDataStorageManager mStorageManager
; 
 156         public ThumbnailGenerationTask(ImageView imageView
, FileDataStorageManager storageManager
, 
 158             // Use a WeakReference to ensure the ImageView can be garbage collected 
 159             mImageViewReference 
= new WeakReference
<ImageView
>(imageView
); 
 160             if (storageManager 
== null
) 
 161                 throw new IllegalArgumentException("storageManager must not be NULL"); 
 162             mStorageManager 
= storageManager
; 
 166         public ThumbnailGenerationTask(ImageView imageView
, FileDataStorageManager storageManager
, 
 167                                        Account account
, ProgressBar progressWheel
) { 
 168         this(imageView
, storageManager
, account
); 
 169         mProgressWheelRef 
= new WeakReference
<ProgressBar
>(progressWheel
); 
 172         public ThumbnailGenerationTask(ImageView imageView
) { 
 173             // Use a WeakReference to ensure the ImageView can be garbage collected 
 174             mImageViewReference 
= new WeakReference
<ImageView
>(imageView
); 
 178         protected Bitmap 
doInBackground(Object
... params
) { 
 179             Bitmap thumbnail 
= null
; 
 182                 if (mAccount 
!= null
) { 
 183                     OwnCloudAccount ocAccount 
= new OwnCloudAccount(mAccount
, 
 184                             MainApp
.getAppContext()); 
 185                     mClient 
= OwnCloudClientManagerFactory
.getDefaultSingleton(). 
 186                             getClientFor(ocAccount
, MainApp
.getAppContext()); 
 190                 mIsThumbnail 
= (Boolean
) params
[1]; 
 193                 if (mFile 
instanceof OCFile
) { 
 194                     thumbnail 
= doOCFileInBackground(mIsThumbnail
); 
 195                 }  else if (mFile 
instanceof File
) { 
 196                     thumbnail 
= doFileInBackground(mIsThumbnail
); 
 202                     // the app should never break due to a problem with thumbnails 
 203                     Log_OC
.e(TAG
, "Generation of thumbnail for " + mFile 
+ " failed", t
); 
 204                     if (t 
instanceof OutOfMemoryError
) { 
 212         protected void onPostExecute(Bitmap bitmap
){ 
 217             if (bitmap 
!= null
) { 
 218                 final ImageView imageView 
= mImageViewReference
.get(); 
 219                 final ThumbnailGenerationTask bitmapWorkerTask 
= getBitmapWorkerTask(imageView
); 
 220                 if (this == bitmapWorkerTask
) { 
 222                     if (mFile 
instanceof OCFile
){ 
 223                         tagId 
= String
.valueOf(((OCFile
)mFile
).getFileId()); 
 224                     } else if (mFile 
instanceof File
){ 
 225                         tagId 
= String
.valueOf(mFile
.hashCode()); 
 227                     if (String
.valueOf(imageView
.getTag()).equals(tagId
)) { 
 228                         if (mProgressWheelRef 
!= null
) { 
 229                             final ProgressBar progressWheel 
= mProgressWheelRef
.get(); 
 230                             if (progressWheel 
!= null
) { 
 231                                 progressWheel
.setVisibility(View
.GONE
); 
 234                         imageView
.setImageBitmap(bitmap
); 
 235                         imageView
.setVisibility(View
.VISIBLE
); 
 242          * Add thumbnail to cache 
 243          * @param imageKey: thumb key 
 244          * @param bitmap:   image for extracting thumbnail 
 245          * @param path:     image path 
 246          * @param pxW:       thumbnail width 
 247          * @param pxH:       thumbnail height 
 250         private Bitmap 
addThumbnailToCache(String imageKey
, Bitmap bitmap
, String path
, int pxW
, int pxH
){ 
 252             Bitmap thumbnail 
= ThumbnailUtils
.extractThumbnail(bitmap
, pxW
, pxH
); 
 254             // Rotate image, obeying exif tag 
 255             thumbnail 
= BitmapUtils
.rotateImage(thumbnail
,path
); 
 257             // Add thumbnail to cache 
 258             addBitmapToCache(imageKey
, thumbnail
); 
 264          * Converts size of file icon from dp to pixel 
 267         private int getThumbnailDimension(){ 
 268             // Converts dp to pixel 
 269             Resources r 
= MainApp
.getAppContext().getResources(); 
 270             return Math
.round(r
.getDimension(R
.dimen
.file_icon_size_grid
)); 
 273         private Point 
getScreenDimension(){ 
 274             WindowManager wm 
= (WindowManager
) MainApp
.getAppContext().getSystemService(Context
.WINDOW_SERVICE
); 
 275             Display display 
= wm
.getDefaultDisplay(); 
 276             Point test 
= new Point(); 
 277             display
.getSize(test
); 
 281         private Bitmap 
doOCFileInBackground(Boolean isThumbnail
) { 
 282             Bitmap thumbnail 
= null
; 
 283             OCFile file 
= (OCFile
)mFile
; 
 285             // distinguish between thumbnail and resized image 
 286             String temp 
= String
.valueOf(file
.getRemoteId()); 
 293             final String imageKey 
= temp
; 
 295             // Check disk cache in background thread 
 296             thumbnail 
= getBitmapFromDiskCache(imageKey
); 
 298             // Not found in disk cache 
 299             if (thumbnail 
== null 
|| file
.needsUpdateThumbnail()) { 
 303                     pxW 
= pxH 
= getThumbnailDimension(); 
 305                     Point p 
= getScreenDimension(); 
 311                     Bitmap bitmap 
= BitmapUtils
.decodeSampledBitmapFromFile( 
 312                             file
.getStoragePath(), pxW
, pxH
); 
 314                     if (bitmap 
!= null
) { 
 315                         thumbnail 
= addThumbnailToCache(imageKey
, bitmap
, file
.getStoragePath(), pxW
, pxH
); 
 317                         file
.setNeedsUpdateThumbnail(false
); 
 318                         mStorageManager
.saveFile(file
); 
 322                     // Download thumbnail from server 
 323                     OwnCloudVersion serverOCVersion 
= AccountUtils
.getServerVersion(mAccount
); 
 324                     if (mClient 
!= null 
&& serverOCVersion 
!= null
) { 
 325                         if (serverOCVersion
.supportsRemoteThumbnails()) { 
 327                                 String uri 
= mClient
.getBaseUri() + "" + 
 328                                         "/index.php/apps/files/api/v1/thumbnail/" + 
 329                                         pxW 
+ "/" + pxH 
+ Uri
.encode(file
.getRemotePath(), "/"); 
 330                                 Log_OC
.d("Thumbnail", "URI: " + uri
); 
 331                                 GetMethod get 
= new GetMethod(uri
); 
 332                                 int status 
= mClient
.executeMethod(get
); 
 333                                 if (status 
== HttpStatus
.SC_OK
) { 
 334 //                                    byte[] bytes = get.getResponseBody(); 
 335 //                                    Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, 
 337                                     InputStream inputStream 
= get
.getResponseBodyAsStream(); 
 338                                     Bitmap bitmap 
= BitmapFactory
.decodeStream(inputStream
); 
 339                                     thumbnail 
= ThumbnailUtils
.extractThumbnail(bitmap
, pxW
, pxH
); 
 340                                     byte[] bytes 
= get
.getResponseBody(); 
 343                                         thumbnail 
= ThumbnailUtils
.extractThumbnail(bitmap
, pxW
, pxH
); 
 348                                     // Add thumbnail to cache 
 349                                     if (thumbnail 
!= null
) { 
 350                                         addBitmapToCache(imageKey
, thumbnail
); 
 353                             } catch (Exception e
) { 
 357                             Log_OC
.d(TAG
, "Server too old"); 
 367         private Bitmap 
doFileInBackground(Boolean mIsThumbnail
) { 
 368             File file 
= (File
)mFile
; 
 370             // distinguish between thumbnail and resized image 
 371             String temp 
= String
.valueOf(file
.hashCode()); 
 378             final String imageKey 
= temp
; 
 380             // Check disk cache in background thread 
 381             Bitmap thumbnail 
= getBitmapFromDiskCache(imageKey
); 
 383             // Not found in disk cache 
 384             if (thumbnail 
== null
) { 
 388                     pxW 
= pxH 
= getThumbnailDimension(); 
 390                     Point p 
= getScreenDimension(); 
 395                 Bitmap bitmap 
= BitmapUtils
.decodeSampledBitmapFromFile( 
 396                         file
.getAbsolutePath(), pxW
, pxH
); 
 398                 if (bitmap 
!= null
) { 
 399                     thumbnail 
= addThumbnailToCache(imageKey
, bitmap
, file
.getPath(), pxW
, pxH
); 
 407     public static boolean cancelPotentialWork(Object file
, ImageView imageView
) { 
 408         final ThumbnailGenerationTask bitmapWorkerTask 
= getBitmapWorkerTask(imageView
); 
 410         if (bitmapWorkerTask 
!= null
) { 
 411             final Object bitmapData 
= bitmapWorkerTask
.mFile
; 
 412             // If bitmapData is not yet set or it differs from the new data 
 413             if (bitmapData 
== null 
|| bitmapData 
!= file
) { 
 414                 // Cancel previous task 
 415                 bitmapWorkerTask
.cancel(true
); 
 417                 // The same work is already in progress 
 421         // No task associated with the ImageView, or an existing task was cancelled 
 425     public static ThumbnailGenerationTask 
getBitmapWorkerTask(ImageView imageView
) { 
 426         if (imageView 
!= null
) { 
 427             final Drawable drawable 
= imageView
.getDrawable(); 
 428             if (drawable 
instanceof AsyncDrawable
) { 
 429                 final AsyncDrawable asyncDrawable 
= (AsyncDrawable
) drawable
; 
 430                 return asyncDrawable
.getBitmapWorkerTask(); 
 436     public static class AsyncDrawable 
extends BitmapDrawable 
{ 
 437         private final WeakReference
<ThumbnailGenerationTask
> bitmapWorkerTaskReference
; 
 439         public AsyncDrawable( 
 440                 Resources res
, Bitmap bitmap
, ThumbnailGenerationTask bitmapWorkerTask
 
 444             bitmapWorkerTaskReference 
= 
 445                     new WeakReference
<ThumbnailGenerationTask
>(bitmapWorkerTask
); 
 448         public ThumbnailGenerationTask 
getBitmapWorkerTask() { 
 449             return bitmapWorkerTaskReference
.get();