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
.res
.Resources
; 
  33 import android
.graphics
.Bitmap
; 
  34 import android
.graphics
.Bitmap
.CompressFormat
; 
  35 import android
.graphics
.BitmapFactory
; 
  36 import android
.graphics
.Canvas
; 
  37 import android
.graphics
.drawable
.BitmapDrawable
; 
  38 import android
.graphics
.drawable
.ColorDrawable
; 
  39 import android
.graphics
.drawable
.Drawable
; 
  40 import android
.media
.ThumbnailUtils
; 
  41 import android
.net
.Uri
; 
  42 import android
.os
.AsyncTask
; 
  43 import android
.widget
.ImageView
; 
  45 import com
.owncloud
.android
.MainApp
; 
  46 import com
.owncloud
.android
.R
; 
  47 import com
.owncloud
.android
.authentication
.AccountUtils
; 
  48 import com
.owncloud
.android
.lib
.common
.OwnCloudAccount
; 
  49 import com
.owncloud
.android
.lib
.common
.OwnCloudClient
; 
  50 import com
.owncloud
.android
.lib
.common
.OwnCloudClientManagerFactory
; 
  51 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
; 
  52 import com
.owncloud
.android
.lib
.resources
.status
.OwnCloudVersion
; 
  53 import com
.owncloud
.android
.ui
.adapter
.DiskLruImageCache
; 
  54 import com
.owncloud
.android
.utils
.BitmapUtils
; 
  55 import com
.owncloud
.android
.utils
.DisplayUtils
; 
  58  * Manager for concurrent access to thumbnails cache. 
  60 public class ThumbnailsCacheManager 
{ 
  62     private static final String TAG 
= ThumbnailsCacheManager
.class.getSimpleName(); 
  64     private static final String CACHE_FOLDER 
= "thumbnailCache"; 
  66     private static final Object mThumbnailsDiskCacheLock 
= new Object(); 
  67     private static DiskLruImageCache mThumbnailCache 
= null
; 
  68     private static boolean mThumbnailCacheStarting 
= true
; 
  70     private static final int DISK_CACHE_SIZE 
= 1024 * 1024 * 10; // 10MB 
  71     private static final CompressFormat mCompressFormat 
= CompressFormat
.JPEG
; 
  72     private static final int mCompressQuality 
= 70; 
  73     private static OwnCloudClient mClient 
= null
; 
  75     public static Bitmap mDefaultImg 
=  
  76             BitmapFactory
.decodeResource( 
  77                     MainApp
.getAppContext().getResources(),  
  78                     DisplayUtils
.getFileTypeIconId("image/png", "default.png") 
  82     public static class InitDiskCacheTask 
extends AsyncTask
<File
, Void
, Void
> { 
  85         protected Void 
doInBackground(File
... params
) { 
  86             synchronized (mThumbnailsDiskCacheLock
) { 
  87                 mThumbnailCacheStarting 
= true
; 
  89                 if (mThumbnailCache 
== null
) { 
  91                         // Check if media is mounted or storage is built-in, if so,  
  92                         // try and use external cache dir; otherwise use internal cache dir 
  93                         final String cachePath 
=  
  94                                 MainApp
.getAppContext().getExternalCacheDir().getPath() +  
  95                                 File
.separator 
+ CACHE_FOLDER
; 
  96                         Log_OC
.d(TAG
, "create dir: " + cachePath
); 
  97                         final File diskCacheDir 
= new File(cachePath
); 
  98                         mThumbnailCache 
= new DiskLruImageCache( 
 104                     } catch (Exception e
) { 
 105                         Log_OC
.d(TAG
, "Thumbnail cache could not be opened ", e
); 
 106                         mThumbnailCache 
= null
; 
 109                 mThumbnailCacheStarting 
= false
; // Finished initialization 
 110                 mThumbnailsDiskCacheLock
.notifyAll(); // Wake any waiting threads 
 117     public static void addBitmapToCache(String key
, Bitmap bitmap
) { 
 118         synchronized (mThumbnailsDiskCacheLock
) { 
 119             if (mThumbnailCache 
!= null
) { 
 120                 mThumbnailCache
.put(key
, bitmap
); 
 126     public static Bitmap 
getBitmapFromDiskCache(String key
) { 
 127         synchronized (mThumbnailsDiskCacheLock
) { 
 128             // Wait while disk cache is started from background thread 
 129             while (mThumbnailCacheStarting
) { 
 131                     mThumbnailsDiskCacheLock
.wait(); 
 132                 } catch (InterruptedException e
) { 
 133                     Log_OC
.e(TAG
, "Wait in mThumbnailsDiskCacheLock was interrupted", e
); 
 136             if (mThumbnailCache 
!= null
) { 
 137                 return mThumbnailCache
.getBitmap(key
); 
 143     public static class ThumbnailGenerationTask 
extends AsyncTask
<Object
, Void
, Bitmap
> { 
 144         private final WeakReference
<ImageView
> mImageViewReference
; 
 145         private static Account mAccount
; 
 146         private Object mFile
; 
 147         private FileDataStorageManager mStorageManager
; 
 150         public ThumbnailGenerationTask(ImageView imageView
, FileDataStorageManager storageManager
, 
 152             // Use a WeakReference to ensure the ImageView can be garbage collected 
 153             mImageViewReference 
= new WeakReference
<ImageView
>(imageView
); 
 154             if (storageManager 
== null
) 
 155                 throw new IllegalArgumentException("storageManager must not be NULL"); 
 156             mStorageManager 
= storageManager
; 
 160         public ThumbnailGenerationTask(ImageView imageView
) { 
 161             // Use a WeakReference to ensure the ImageView can be garbage collected 
 162             mImageViewReference 
= new WeakReference
<ImageView
>(imageView
); 
 166         protected Bitmap 
doInBackground(Object
... params
) { 
 167             Bitmap thumbnail 
= null
; 
 170                 if (mAccount 
!= null
) { 
 171                     OwnCloudAccount ocAccount 
= new OwnCloudAccount(mAccount
, 
 172                             MainApp
.getAppContext()); 
 173                     mClient 
= OwnCloudClientManagerFactory
.getDefaultSingleton(). 
 174                             getClientFor(ocAccount
, MainApp
.getAppContext()); 
 179                 if (mFile 
instanceof OCFile
) { 
 180                     thumbnail 
= doOCFileInBackground(); 
 181                 }  else if (mFile 
instanceof File
) { 
 182                     thumbnail 
= doFileInBackground(); 
 183                 //} else {  do nothing 
 187                     // the app should never break due to a problem with thumbnails 
 188                     Log_OC
.e(TAG
, "Generation of thumbnail for " + mFile 
+ " failed", t
); 
 189                     if (t 
instanceof OutOfMemoryError
) { 
 197         protected void onPostExecute(Bitmap bitmap
){ 
 202             if (bitmap 
!= null
) { 
 203                 final ImageView imageView 
= mImageViewReference
.get(); 
 204                 final ThumbnailGenerationTask bitmapWorkerTask 
= getBitmapWorkerTask(imageView
); 
 205                 if (this == bitmapWorkerTask
) { 
 207                     if (mFile 
instanceof OCFile
){ 
 208                         tagId 
= String
.valueOf(((OCFile
)mFile
).getFileId()); 
 209                     } else if (mFile 
instanceof File
){ 
 210                         tagId 
= String
.valueOf(mFile
.hashCode()); 
 212                     if (String
.valueOf(imageView
.getTag()).equals(tagId
)) { 
 213                         imageView
.setImageBitmap(bitmap
); 
 220          * Add thumbnail to cache 
 221          * @param imageKey: thumb key 
 222          * @param bitmap:   image for extracting thumbnail 
 223          * @param path:     image path 
 224          * @param px:       thumbnail dp 
 227         private Bitmap 
addThumbnailToCache(String imageKey
, Bitmap bitmap
, String path
, int px
){ 
 229             Bitmap thumbnail 
= ThumbnailUtils
.extractThumbnail(bitmap
, px
, px
); 
 231             // Rotate image, obeying exif tag 
 232             thumbnail 
= BitmapUtils
.rotateImage(thumbnail
,path
); 
 234             // Add thumbnail to cache 
 235             addBitmapToCache(imageKey
, thumbnail
); 
 241          * Converts size of file icon from dp to pixel 
 244         private int getThumbnailDimension(){ 
 245             // Converts dp to pixel 
 246             Resources r 
= MainApp
.getAppContext().getResources(); 
 247             return Math
.round(r
.getDimension(R
.dimen
.file_icon_size_grid
)); 
 250         private Bitmap 
doOCFileInBackground() { 
 251             OCFile file 
= (OCFile
)mFile
; 
 253             final String imageKey 
= String
.valueOf(file
.getRemoteId()); 
 255             // Check disk cache in background thread 
 256             Bitmap thumbnail 
= getBitmapFromDiskCache(imageKey
); 
 258             // Not found in disk cache 
 259             if (thumbnail 
== null 
|| file
.needsUpdateThumbnail()) { 
 261                 int px 
= getThumbnailDimension(); 
 264                     Bitmap temp 
= BitmapUtils
.decodeSampledBitmapFromFile( 
 265                             file
.getStoragePath(), px
, px
); 
 266                     Bitmap bitmap 
= ThumbnailUtils
.extractThumbnail(temp
, px
, px
); 
 268                     if (bitmap 
!= null
) { 
 270                         if (file
.getMimetype().equalsIgnoreCase("image/png")) { 
 271                             bitmap 
= handlePNG(bitmap
, px
); 
 274                         thumbnail 
= addThumbnailToCache(imageKey
, bitmap
, file
.getStoragePath(), px
); 
 276                         file
.setNeedsUpdateThumbnail(false
); 
 277                         mStorageManager
.saveFile(file
); 
 281                     // Download thumbnail from server 
 282                     OwnCloudVersion serverOCVersion 
= AccountUtils
.getServerVersion(mAccount
); 
 283                     if (mClient 
!= null 
&& serverOCVersion 
!= null
) { 
 284                         if (serverOCVersion
.supportsRemoteThumbnails()) { 
 286                                 String uri 
= mClient
.getBaseUri() + "" + 
 287                                         "/index.php/apps/files/api/v1/thumbnail/" + 
 288                                         px 
+ "/" + px 
+ Uri
.encode(file
.getRemotePath(), "/"); 
 289                                 Log_OC
.d("Thumbnail", "URI: " + uri
); 
 290                                 GetMethod get 
= new GetMethod(uri
); 
 291                                 int status 
= mClient
.executeMethod(get
); 
 292                                 if (status 
== HttpStatus
.SC_OK
) { 
 293                                     InputStream inputStream 
= get
.getResponseBodyAsStream(); 
 294                                     Bitmap bitmap 
= BitmapFactory
.decodeStream(inputStream
); 
 295                                     thumbnail 
= ThumbnailUtils
.extractThumbnail(bitmap
, px
, px
); 
 298                                     if (file
.getMimetype().equalsIgnoreCase("image/png")) { 
 299                                         thumbnail 
= handlePNG(thumbnail
, px
); 
 302                                     // Add thumbnail to cache 
 303                                     if (thumbnail 
!= null
) { 
 304                                         addBitmapToCache(imageKey
, thumbnail
); 
 307                             } catch (Exception e
) { 
 311                             Log_OC
.d(TAG
, "Server too old"); 
 321         private Bitmap 
handlePNG(Bitmap bitmap
, int px
){ 
 322             Bitmap resultBitmap 
= Bitmap
.createBitmap(px
, 
 324                     Bitmap
.Config
.ARGB_8888
); 
 325             Canvas c 
= new Canvas(resultBitmap
); 
 327             c
.drawColor(MainApp
.getAppContext().getResources(). 
 328                     getColor(R
.color
.background_color
)); 
 329             c
.drawBitmap(bitmap
, 0, 0, null
); 
 334         private Bitmap 
doFileInBackground() { 
 335             File file 
= (File
)mFile
; 
 337             final String imageKey 
= String
.valueOf(file
.hashCode()); 
 339             // Check disk cache in background thread 
 340             Bitmap thumbnail 
= getBitmapFromDiskCache(imageKey
); 
 342             // Not found in disk cache 
 343             if (thumbnail 
== null
) { 
 345                 int px 
= getThumbnailDimension(); 
 347                 Bitmap bitmap 
= BitmapUtils
.decodeSampledBitmapFromFile( 
 348                         file
.getAbsolutePath(), px
, px
); 
 350                 if (bitmap 
!= null
) { 
 351                     thumbnail 
= addThumbnailToCache(imageKey
, bitmap
, file
.getPath(), px
); 
 359     public static boolean cancelPotentialWork(Object file
, ImageView imageView
) { 
 360         final ThumbnailGenerationTask bitmapWorkerTask 
= getBitmapWorkerTask(imageView
); 
 362         if (bitmapWorkerTask 
!= null
) { 
 363             final Object bitmapData 
= bitmapWorkerTask
.mFile
; 
 364             // If bitmapData is not yet set or it differs from the new data 
 365             if (bitmapData 
== null 
|| bitmapData 
!= file
) { 
 366                 // Cancel previous task 
 367                 bitmapWorkerTask
.cancel(true
); 
 369                 // The same work is already in progress 
 373         // No task associated with the ImageView, or an existing task was cancelled 
 377     public static ThumbnailGenerationTask 
getBitmapWorkerTask(ImageView imageView
) { 
 378         if (imageView 
!= null
) { 
 379             final Drawable drawable 
= imageView
.getDrawable(); 
 380             if (drawable 
instanceof AsyncDrawable
) { 
 381                 final AsyncDrawable asyncDrawable 
= (AsyncDrawable
) drawable
; 
 382                 return asyncDrawable
.getBitmapWorkerTask(); 
 388     public static class AsyncDrawable 
extends BitmapDrawable 
{ 
 389         private final WeakReference
<ThumbnailGenerationTask
> bitmapWorkerTaskReference
; 
 391         public AsyncDrawable( 
 392                 Resources res
, Bitmap bitmap
, ThumbnailGenerationTask bitmapWorkerTask
 
 396             bitmapWorkerTaskReference 
= 
 397                     new WeakReference
<ThumbnailGenerationTask
>(bitmapWorkerTask
); 
 400         public ThumbnailGenerationTask 
getBitmapWorkerTask() { 
 401             return bitmapWorkerTaskReference
.get();