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
.Canvas
; 
  39 import android
.graphics
.Point
; 
  40 import android
.graphics
.drawable
.BitmapDrawable
; 
  41 import android
.graphics
.drawable
.ColorDrawable
; 
  42 import android
.graphics
.drawable
.Drawable
; 
  43 import android
.media
.ThumbnailUtils
; 
  44 import android
.net
.Uri
; 
  45 import android
.os
.AsyncTask
; 
  46 import android
.view
.Display
; 
  47 import android
.view
.View
; 
  48 import android
.view
.WindowManager
; 
  49 import android
.widget
.ImageView
; 
  50 import android
.widget
.ProgressBar
; 
  52 import com
.owncloud
.android
.MainApp
; 
  53 import com
.owncloud
.android
.R
; 
  54 import com
.owncloud
.android
.authentication
.AccountUtils
; 
  55 import com
.owncloud
.android
.lib
.common
.OwnCloudAccount
; 
  56 import com
.owncloud
.android
.lib
.common
.OwnCloudClient
; 
  57 import com
.owncloud
.android
.lib
.common
.OwnCloudClientManagerFactory
; 
  58 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
; 
  59 import com
.owncloud
.android
.lib
.resources
.status
.OwnCloudVersion
; 
  60 import com
.owncloud
.android
.ui
.adapter
.DiskLruImageCache
; 
  61 import com
.owncloud
.android
.utils
.BitmapUtils
; 
  62 import com
.owncloud
.android
.utils
.DisplayUtils
; 
  65  * Manager for concurrent access to thumbnails cache. 
  67 public class ThumbnailsCacheManager 
{ 
  69     private static final String TAG 
= ThumbnailsCacheManager
.class.getSimpleName(); 
  71     private static final String CACHE_FOLDER 
= "thumbnailCache"; 
  73     private static final Object mThumbnailsDiskCacheLock 
= new Object(); 
  74     private static DiskLruImageCache mThumbnailCache 
= null
; 
  75     private static boolean mThumbnailCacheStarting 
= true
; 
  77     private static final int DISK_CACHE_SIZE 
= 1024 * 1024 * 10; // 10MB 
  78     private static final CompressFormat mCompressFormat 
= CompressFormat
.JPEG
; 
  79     private static final int mCompressQuality 
= 70; 
  80     private static OwnCloudClient mClient 
= null
; 
  82     public static Bitmap mDefaultImg 
=  
  83             BitmapFactory
.decodeResource( 
  84                     MainApp
.getAppContext().getResources(), 
  89     public static class InitDiskCacheTask 
extends AsyncTask
<File
, Void
, Void
> { 
  92         protected Void 
doInBackground(File
... params
) { 
  93             synchronized (mThumbnailsDiskCacheLock
) { 
  94                 mThumbnailCacheStarting 
= true
; 
  96                 if (mThumbnailCache 
== null
) { 
  98                         // Check if media is mounted or storage is built-in, if so,  
  99                         // try and use external cache dir; otherwise use internal cache dir 
 100                         final String cachePath 
=  
 101                                 MainApp
.getAppContext().getExternalCacheDir().getPath() +  
 102                                 File
.separator 
+ CACHE_FOLDER
; 
 103                         Log_OC
.d(TAG
, "create dir: " + cachePath
); 
 104                         final File diskCacheDir 
= new File(cachePath
); 
 105                         mThumbnailCache 
= new DiskLruImageCache( 
 111                     } catch (Exception e
) { 
 112                         Log_OC
.d(TAG
, "Thumbnail cache could not be opened ", e
); 
 113                         mThumbnailCache 
= null
; 
 116                 mThumbnailCacheStarting 
= false
; // Finished initialization 
 117                 mThumbnailsDiskCacheLock
.notifyAll(); // Wake any waiting threads 
 124     public static void addBitmapToCache(String key
, Bitmap bitmap
) { 
 125         synchronized (mThumbnailsDiskCacheLock
) { 
 126             if (mThumbnailCache 
!= null
) { 
 127                 mThumbnailCache
.put(key
, bitmap
); 
 133     public static Bitmap 
getBitmapFromDiskCache(String key
) { 
 134         synchronized (mThumbnailsDiskCacheLock
) { 
 135             // Wait while disk cache is started from background thread 
 136             while (mThumbnailCacheStarting
) { 
 138                     mThumbnailsDiskCacheLock
.wait(); 
 139                 } catch (InterruptedException e
) { 
 140                     Log_OC
.e(TAG
, "Wait in mThumbnailsDiskCacheLock was interrupted", e
); 
 143             if (mThumbnailCache 
!= null
) { 
 144                 return mThumbnailCache
.getBitmap(key
); 
 150     public static class ThumbnailGenerationTask 
extends AsyncTask
<Object
, Void
, Bitmap
> { 
 151         private final WeakReference
<ImageView
> mImageViewReference
; 
 152         private WeakReference
<ProgressBar
> mProgressWheelRef
; 
 153         private static Account mAccount
; 
 154         private Object mFile
; 
 155         private Boolean mIsThumbnail
; 
 156         private FileDataStorageManager mStorageManager
; 
 158         public ThumbnailGenerationTask(ImageView imageView
, FileDataStorageManager storageManager
, 
 160             // Use a WeakReference to ensure the ImageView can be garbage collected 
 161             mImageViewReference 
= new WeakReference
<ImageView
>(imageView
); 
 162             if (storageManager 
== null
) 
 163                 throw new IllegalArgumentException("storageManager must not be NULL"); 
 164             mStorageManager 
= storageManager
; 
 168         public ThumbnailGenerationTask(ImageView imageView
, FileDataStorageManager storageManager
, 
 169                                        Account account
, ProgressBar progressWheel
) { 
 170         this(imageView
, storageManager
, account
); 
 171         mProgressWheelRef 
= new WeakReference
<ProgressBar
>(progressWheel
); 
 174         public ThumbnailGenerationTask(ImageView imageView
) { 
 175             // Use a WeakReference to ensure the ImageView can be garbage collected 
 176             mImageViewReference 
= new WeakReference
<ImageView
>(imageView
); 
 180         protected Bitmap 
doInBackground(Object
... params
) { 
 181             Bitmap thumbnail 
= null
; 
 184                 if (mAccount 
!= null
) { 
 185                     OwnCloudAccount ocAccount 
= new OwnCloudAccount(mAccount
, 
 186                             MainApp
.getAppContext()); 
 187                     mClient 
= OwnCloudClientManagerFactory
.getDefaultSingleton(). 
 188                             getClientFor(ocAccount
, MainApp
.getAppContext()); 
 192                 mIsThumbnail 
= (Boolean
) params
[1]; 
 195                 if (mFile 
instanceof OCFile
) { 
 196                     thumbnail 
= doOCFileInBackground(mIsThumbnail
); 
 197                 }  else if (mFile 
instanceof File
) { 
 198                     thumbnail 
= doFileInBackground(mIsThumbnail
); 
 204                     // the app should never break due to a problem with thumbnails 
 205                     Log_OC
.e(TAG
, "Generation of thumbnail for " + mFile 
+ " failed", t
); 
 206                     if (t 
instanceof OutOfMemoryError
) { 
 214         protected void onPostExecute(Bitmap bitmap
){ 
 215             if (bitmap 
!= null
) { 
 216                 final ImageView imageView 
= mImageViewReference
.get(); 
 217                 final ThumbnailGenerationTask bitmapWorkerTask 
= getBitmapWorkerTask(imageView
); 
 218                 if (this == bitmapWorkerTask
) { 
 220                     if (mFile 
instanceof OCFile
){ 
 221                         tagId 
= String
.valueOf(((OCFile
)mFile
).getFileId()); 
 222                     } else if (mFile 
instanceof File
){ 
 223                         tagId 
= String
.valueOf(mFile
.hashCode()); 
 225                     if (String
.valueOf(imageView
.getTag()).equals(tagId
)) { 
 226                         if (mProgressWheelRef 
!= null
) { 
 227                             final ProgressBar progressWheel 
= mProgressWheelRef
.get(); 
 228                             if (progressWheel 
!= null
) { 
 229                                 progressWheel
.setVisibility(View
.GONE
); 
 232                         imageView
.setImageBitmap(bitmap
); 
 233                         imageView
.setVisibility(View
.VISIBLE
); 
 240          * Add thumbnail to cache 
 241          * @param imageKey: thumb key 
 242          * @param bitmap:   image for extracting thumbnail 
 243          * @param path:     image path 
 244          * @param pxW:       thumbnail width 
 245          * @param pxH:       thumbnail height 
 248         private Bitmap 
addThumbnailToCache(String imageKey
, Bitmap bitmap
, String path
, int pxW
, int pxH
){ 
 250             Bitmap thumbnail 
= ThumbnailUtils
.extractThumbnail(bitmap
, pxW
, pxH
); 
 252             // Rotate image, obeying exif tag 
 253             thumbnail 
= BitmapUtils
.rotateImage(thumbnail
,path
); 
 255             // Add thumbnail to cache 
 256             addBitmapToCache(imageKey
, thumbnail
); 
 262          * Converts size of file icon from dp to pixel 
 265         private int getThumbnailDimension(){ 
 266             // Converts dp to pixel 
 267             Resources r 
= MainApp
.getAppContext().getResources(); 
 268             return Math
.round(r
.getDimension(R
.dimen
.file_icon_size_grid
)); 
 271         private Point 
getScreenDimension(){ 
 272             WindowManager wm 
= (WindowManager
) MainApp
.getAppContext().getSystemService(Context
.WINDOW_SERVICE
); 
 273             Display display 
= wm
.getDefaultDisplay(); 
 274             Point test 
= new Point(); 
 275             display
.getSize(test
); 
 279         private Bitmap 
doOCFileInBackground(Boolean isThumbnail
) { 
 280             Bitmap thumbnail 
= null
; 
 281             OCFile file 
= (OCFile
)mFile
; 
 283             // distinguish between thumbnail and resized image 
 284             String temp 
= String
.valueOf(file
.getRemoteId()); 
 291             final String imageKey 
= temp
; 
 293             // Check disk cache in background thread 
 294             thumbnail 
= getBitmapFromDiskCache(imageKey
); 
 296             // Not found in disk cache 
 297             if (thumbnail 
== null 
|| file
.needsUpdateThumbnail()) { 
 301                     pxW 
= pxH 
= getThumbnailDimension(); 
 303                     Point p 
= getScreenDimension(); 
 309                     Bitmap tempBitmap 
= BitmapUtils
.decodeSampledBitmapFromFile( 
 310                             file
.getStoragePath(), pxW
, pxH
); 
 311                     Bitmap bitmap 
= ThumbnailUtils
.extractThumbnail(tempBitmap
, pxW
, pxH
); 
 313                     if (bitmap 
!= null
) { 
 315                         if (file
.getMimetype().equalsIgnoreCase("image/png")) { 
 316                             bitmap 
= handlePNG(bitmap
, pxW
); 
 319                         thumbnail 
= addThumbnailToCache(imageKey
, bitmap
, 
 320                                                         file
.getStoragePath(), pxW
, pxH
); 
 322                         file
.setNeedsUpdateThumbnail(false
); 
 323                         mStorageManager
.saveFile(file
); 
 327                     // Download thumbnail from server 
 328                     OwnCloudVersion serverOCVersion 
= AccountUtils
.getServerVersion(mAccount
); 
 329                     if (mClient 
!= null 
&& serverOCVersion 
!= null
) { 
 330                         if (serverOCVersion
.supportsRemoteThumbnails()) { 
 333                                     String uri 
= mClient
.getBaseUri() + "" + 
 334                                             "/index.php/apps/files/api/v1/thumbnail/" + 
 335                                             pxW 
+ "/" + pxH 
+ Uri
.encode(file
.getRemotePath(), "/"); 
 336                                     Log_OC
.d("Thumbnail", "Download URI: " + uri
); 
 337                                     GetMethod get 
= new GetMethod(uri
); 
 338                                     int status 
= mClient
.executeMethod(get
); 
 339                                     if (status 
== HttpStatus
.SC_OK
) { 
 340                                         InputStream inputStream 
= get
.getResponseBodyAsStream(); 
 341                                         Bitmap bitmap 
= BitmapFactory
.decodeStream(inputStream
); 
 342                                         thumbnail 
= ThumbnailUtils
.extractThumbnail(bitmap
, pxW
, pxH
); 
 344                                         Log_OC
.d(TAG
, "Status: " + status
); 
 348                                     if (serverOCVersion
.supportsNativeGallery()){ 
 351                                         gallery 
= "galleryplus"; 
 354                                     String uri 
= mClient
.getBaseUri() + 
 355                                             "/index.php/apps/" + gallery 
+ "/api/preview/" + Integer
.parseInt(file
.getRemoteId().substring(0,8)) + 
 356                                             "/" + pxW 
+ "/" + pxH
; 
 357                                     Log_OC
.d("Thumbnail", "FileName: " + file
.getFileName() + " Download URI: " + uri
); 
 358                                     GetMethod get 
= new GetMethod(uri
); 
 359                                     int status 
= mClient
.executeMethod(get
); 
 360                                     if (status 
== HttpStatus
.SC_OK
) { 
 361                                         InputStream inputStream 
= get
.getResponseBodyAsStream(); 
 362                                         Bitmap bitmap 
= BitmapFactory
.decodeStream(inputStream
); 
 363                                         // Download via gallery app 
 369                                 if (thumbnail 
!= null 
&& file
.getMimetype().equalsIgnoreCase("image/png")) { 
 370                                     thumbnail 
= handlePNG(thumbnail
, pxW
); 
 373                                 // Add thumbnail to cache 
 374                                 if (thumbnail 
!= null
) { 
 375                                     addBitmapToCache(imageKey
, thumbnail
); 
 377                             } catch (Exception e
) { 
 381                             Log_OC
.d(TAG
, "Server too old"); 
 391         private Bitmap 
handlePNG(Bitmap bitmap
, int px
){ 
 392             Bitmap resultBitmap 
= Bitmap
.createBitmap(px
, 
 394                     Bitmap
.Config
.ARGB_8888
); 
 395             Canvas c 
= new Canvas(resultBitmap
); 
 397             c
.drawColor(MainApp
.getAppContext().getResources(). 
 398                     getColor(R
.color
.background_color
)); 
 399             c
.drawBitmap(bitmap
, 0, 0, null
); 
 404         private Bitmap 
doFileInBackground(Boolean mIsThumbnail
) { 
 405             Bitmap thumbnail 
= null
; 
 406             File file 
= (File
)mFile
; 
 408             final String imageKey 
= String
.valueOf(file
.hashCode()); 
 410             // Check disk cache in background thread 
 411             thumbnail 
= getBitmapFromDiskCache(imageKey
); 
 413             // Not found in disk cache 
 414             if (thumbnail 
== null
) { 
 418                     pxW 
= pxH 
= getThumbnailDimension(); 
 420                     Point p 
= getScreenDimension(); 
 425                 Bitmap bitmap 
= BitmapUtils
.decodeSampledBitmapFromFile( 
 426                         file
.getAbsolutePath(), pxW
, pxH
); 
 428                 if (bitmap 
!= null
) { 
 429                     thumbnail 
= addThumbnailToCache(imageKey
, bitmap
, file
.getPath(), pxW
, pxH
); 
 437     public static boolean cancelPotentialWork(Object file
, ImageView imageView
) { 
 438         final ThumbnailGenerationTask bitmapWorkerTask 
= getBitmapWorkerTask(imageView
); 
 440         if (bitmapWorkerTask 
!= null
) { 
 441             final Object bitmapData 
= bitmapWorkerTask
.mFile
; 
 442             // If bitmapData is not yet set or it differs from the new data 
 443             if (bitmapData 
== null 
|| bitmapData 
!= file
) { 
 444                 // Cancel previous task 
 445                 bitmapWorkerTask
.cancel(true
); 
 446                 Log_OC
.v(TAG
, "Cancelled generation of thumbnail for a reused imageView"); 
 448                 // The same work is already in progress 
 452         // No task associated with the ImageView, or an existing task was cancelled 
 456     public static ThumbnailGenerationTask 
getBitmapWorkerTask(ImageView imageView
) { 
 457         if (imageView 
!= null
) { 
 458             final Drawable drawable 
= imageView
.getDrawable(); 
 459             if (drawable 
instanceof AsyncDrawable
) { 
 460                 final AsyncDrawable asyncDrawable 
= (AsyncDrawable
) drawable
; 
 461                 return asyncDrawable
.getBitmapWorkerTask(); 
 467     public static class AsyncDrawable 
extends BitmapDrawable 
{ 
 468         private final WeakReference
<ThumbnailGenerationTask
> bitmapWorkerTaskReference
; 
 470         public AsyncDrawable( 
 471                 Resources res
, Bitmap bitmap
, ThumbnailGenerationTask bitmapWorkerTask
 
 475             bitmapWorkerTaskReference 
= 
 476                     new WeakReference
<ThumbnailGenerationTask
>(bitmapWorkerTask
); 
 479         public ThumbnailGenerationTask 
getBitmapWorkerTask() { 
 480             return bitmapWorkerTaskReference
.get();