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
.io
.IOException
;
22 import java
.lang
.ref
.WeakReference
;
24 import org
.apache
.commons
.httpclient
.HttpStatus
;
25 import org
.apache
.commons
.httpclient
.methods
.GetMethod
;
27 import android
.accounts
.Account
;
28 import android
.accounts
.AccountManager
;
29 import android
.accounts
.AuthenticatorException
;
30 import android
.accounts
.OperationCanceledException
;
31 import android
.content
.Context
;
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
.drawable
.BitmapDrawable
;
37 import android
.graphics
.drawable
.Drawable
;
38 import android
.media
.ThumbnailUtils
;
39 import android
.net
.Uri
;
40 import android
.os
.AsyncTask
;
41 import android
.util
.TypedValue
;
42 import android
.widget
.ImageView
;
44 import com
.owncloud
.android
.MainApp
;
45 import com
.owncloud
.android
.lib
.common
.OwnCloudAccount
;
46 import com
.owncloud
.android
.lib
.common
.OwnCloudClient
;
47 import com
.owncloud
.android
.lib
.common
.OwnCloudClientManagerFactory
;
48 import com
.owncloud
.android
.lib
.common
.accounts
.AccountUtils
.AccountNotFoundException
;
49 import com
.owncloud
.android
.lib
.common
.accounts
.AccountUtils
.Constants
;
50 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
;
51 import com
.owncloud
.android
.lib
.resources
.status
.OwnCloudVersion
;
52 import com
.owncloud
.android
.ui
.adapter
.DiskLruImageCache
;
53 import com
.owncloud
.android
.utils
.BitmapUtils
;
54 import com
.owncloud
.android
.utils
.DisplayUtils
;
57 * Manager for concurrent access to thumbnails cache.
59 * @author Tobias Kaminsky
60 * @author David A. Velasco
62 public class ThumbnailsCacheManager
{
64 private static final String TAG
= ThumbnailsCacheManager
.class.getSimpleName();
66 private static final String CACHE_FOLDER
= "thumbnailCache";
67 private static final String MINOR_SERVER_VERSION_FOR_THUMBS
= "7.8.0";
69 private static final Object mThumbnailsDiskCacheLock
= new Object();
70 private static DiskLruImageCache mThumbnailCache
= null
;
71 private static boolean mThumbnailCacheStarting
= true
;
73 private static final int DISK_CACHE_SIZE
= 1024 * 1024 * 10; // 10MB
74 private static final CompressFormat mCompressFormat
= CompressFormat
.JPEG
;
75 private static final int mCompressQuality
= 70;
76 private static OwnCloudClient mClient
= null
;
77 private static String mServerVersion
= null
;
79 public static Bitmap mDefaultImg
=
80 BitmapFactory
.decodeResource(
81 MainApp
.getAppContext().getResources(),
82 DisplayUtils
.getResourceId("image/png", "default.png")
86 public static class InitDiskCacheTask
extends AsyncTask
<File
, Void
, Void
> {
87 private static Account mAccount
;
88 private static Context mContext
;
90 public InitDiskCacheTask(Account account
, Context context
) {
96 protected Void
doInBackground(File
... params
) {
97 synchronized (mThumbnailsDiskCacheLock
) {
98 mThumbnailCacheStarting
= true
;
100 if (mThumbnailCache
== null
) {
102 OwnCloudAccount ocAccount
;
104 if (mAccount
!= null
) {
105 AccountManager accountMgr
= AccountManager
.get(mContext
);
106 mServerVersion
= accountMgr
.getUserData(mAccount
, Constants
.KEY_OC_VERSION
);
107 ocAccount
= new OwnCloudAccount(mAccount
, mContext
);
108 mClient
= OwnCloudClientManagerFactory
.getDefaultSingleton().getClientFor(ocAccount
, mContext
);
110 } catch (AccountNotFoundException e
) {
111 // TODO Auto-generated catch block
113 } catch (AuthenticatorException e
) {
114 // TODO Auto-generated catch block
116 } catch (OperationCanceledException e
) {
117 // TODO Auto-generated catch block
119 } catch (IOException e
) {
120 // TODO Auto-generated catch block
124 // Check if media is mounted or storage is built-in, if so,
125 // try and use external cache dir; otherwise use internal cache dir
126 final String cachePath
=
127 MainApp
.getAppContext().getExternalCacheDir().getPath() +
128 File
.separator
+ CACHE_FOLDER
;
129 Log_OC
.d(TAG
, "create dir: " + cachePath
);
130 final File diskCacheDir
= new File(cachePath
);
131 mThumbnailCache
= new DiskLruImageCache(
137 } catch (Exception e
) {
138 Log_OC
.d(TAG
, "Thumbnail cache could not be opened ", e
);
139 mThumbnailCache
= null
;
142 mThumbnailCacheStarting
= false
; // Finished initialization
143 mThumbnailsDiskCacheLock
.notifyAll(); // Wake any waiting threads
150 public static void addBitmapToCache(String key
, Bitmap bitmap
) {
151 synchronized (mThumbnailsDiskCacheLock
) {
152 if (mThumbnailCache
!= null
) {
153 mThumbnailCache
.put(key
, bitmap
);
159 public static Bitmap
getBitmapFromDiskCache(String key
) {
160 synchronized (mThumbnailsDiskCacheLock
) {
161 // Wait while disk cache is started from background thread
162 while (mThumbnailCacheStarting
) {
164 mThumbnailsDiskCacheLock
.wait();
165 } catch (InterruptedException e
) {}
167 if (mThumbnailCache
!= null
) {
168 return (Bitmap
) mThumbnailCache
.getBitmap(key
);
175 public static boolean cancelPotentialWork(OCFile file
, ImageView imageView
) {
176 final ThumbnailGenerationTask bitmapWorkerTask
= getBitmapWorkerTask(imageView
);
178 if (bitmapWorkerTask
!= null
) {
179 final OCFile bitmapData
= bitmapWorkerTask
.mFile
;
180 // If bitmapData is not yet set or it differs from the new data
181 if (bitmapData
== null
|| bitmapData
!= file
) {
182 // Cancel previous task
183 bitmapWorkerTask
.cancel(true
);
185 // The same work is already in progress
189 // No task associated with the ImageView, or an existing task was cancelled
193 public static ThumbnailGenerationTask
getBitmapWorkerTask(ImageView imageView
) {
194 if (imageView
!= null
) {
195 final Drawable drawable
= imageView
.getDrawable();
196 if (drawable
instanceof AsyncDrawable
) {
197 final AsyncDrawable asyncDrawable
= (AsyncDrawable
) drawable
;
198 return asyncDrawable
.getBitmapWorkerTask();
204 public static class ThumbnailGenerationTask
extends AsyncTask
<OCFile
, Void
, Bitmap
> {
205 private final WeakReference
<ImageView
> mImageViewReference
;
206 private OCFile mFile
;
207 private FileDataStorageManager mStorageManager
;
209 public ThumbnailGenerationTask(ImageView imageView
, FileDataStorageManager storageManager
) {
210 // Use a WeakReference to ensure the ImageView can be garbage collected
211 mImageViewReference
= new WeakReference
<ImageView
>(imageView
);
212 if (storageManager
== null
)
213 throw new IllegalArgumentException("storageManager must not be NULL");
214 mStorageManager
= storageManager
;
217 // Decode image in background.
219 protected Bitmap
doInBackground(OCFile
... params
) {
220 Bitmap thumbnail
= null
;
224 final String imageKey
= String
.valueOf(mFile
.getRemoteId());
226 // Check disk cache in background thread
227 thumbnail
= getBitmapFromDiskCache(imageKey
);
229 // Not found in disk cache
230 if (thumbnail
== null
|| mFile
.needsUpdateThumbnail()) {
231 // Converts dp to pixel
232 Resources r
= MainApp
.getAppContext().getResources();
233 int px
= (int) Math
.round(TypedValue
.applyDimension(
234 TypedValue
.COMPLEX_UNIT_DIP
, 150, r
.getDisplayMetrics()
238 Bitmap bitmap
= BitmapUtils
.decodeSampledBitmapFromFile(
239 mFile
.getStoragePath(), px
, px
);
241 if (bitmap
!= null
) {
242 thumbnail
= ThumbnailUtils
.extractThumbnail(bitmap
, px
, px
);
244 // Add thumbnail to cache
245 addBitmapToCache(imageKey
, thumbnail
);
247 mFile
.setNeedsUpdateThumbnail(false
);
248 mStorageManager
.saveFile(mFile
);
252 // Download thumbnail from server
253 if (mClient
!= null
&& mServerVersion
!= null
) {
254 OwnCloudVersion serverOCVersion
= new OwnCloudVersion(mServerVersion
);
255 if (serverOCVersion
.compareTo(new OwnCloudVersion(MINOR_SERVER_VERSION_FOR_THUMBS
)) >= 0) {
259 String uri
= mClient
.getBaseUri() + "/index.php/apps/files/api/v1/thumbnail/" + px
+ "/" + px
260 + Uri
.encode(mFile
.getRemotePath(), "/");
261 Log_OC
.d("Thumbnail", "URI: " + uri
);
262 GetMethod get
= new GetMethod(uri
);
263 status
= mClient
.executeMethod(get
);
264 if (status
== HttpStatus
.SC_OK
) {
265 byte[] bytes
= get
.getResponseBody();
266 Bitmap bitmap
= BitmapFactory
.decodeByteArray(bytes
, 0, bytes
.length
);
267 thumbnail
= ThumbnailUtils
.extractThumbnail(bitmap
, px
, px
);
269 // Add thumbnail to cache
270 if (thumbnail
!= null
) {
271 addBitmapToCache(imageKey
, thumbnail
);
274 } catch (Exception e
) {
282 } catch (Throwable t
) {
283 // the app should never break due to a problem with thumbnails
284 Log_OC
.e(TAG
, "Generation of thumbnail for " + mFile
+ " failed", t
);
285 if (t
instanceof OutOfMemoryError
) {
293 protected void onPostExecute(Bitmap bitmap
){
298 if (mImageViewReference
!= null
&& bitmap
!= null
) {
299 final ImageView imageView
= mImageViewReference
.get();
300 final ThumbnailGenerationTask bitmapWorkerTask
=
301 getBitmapWorkerTask(imageView
);
302 if (this == bitmapWorkerTask
&& imageView
!= null
) {
303 if (imageView
.getTag().equals(mFile
.getFileId())) {
304 imageView
.setImageBitmap(bitmap
);
312 public static class AsyncDrawable
extends BitmapDrawable
{
313 private final WeakReference
<ThumbnailGenerationTask
> bitmapWorkerTaskReference
;
315 public AsyncDrawable(
316 Resources res
, Bitmap bitmap
, ThumbnailGenerationTask bitmapWorkerTask
320 bitmapWorkerTaskReference
=
321 new WeakReference
<ThumbnailGenerationTask
>(bitmapWorkerTask
);
324 public ThumbnailGenerationTask
getBitmapWorkerTask() {
325 return bitmapWorkerTaskReference
.get();
331 * Remove from cache the remoteId passed
332 * @param fileRemoteId: remote id of mFile passed
334 public static void removeFileFromCache(String fileRemoteId
){
335 synchronized (mThumbnailsDiskCacheLock
) {
336 if (mThumbnailCache
!= null
) {
337 mThumbnailCache
.removeKey(fileRemoteId
);
339 mThumbnailsDiskCacheLock
.notifyAll(); // Wake any waiting threads