1 /* ownCloud Android client application
2 * @author Tobias Kaminsky
3 * @author David A. Velasco
4 * Copyright (C) 2012-2014 ownCloud Inc.
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2,
8 * as published by the Free Software Foundation.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 package com
.owncloud
.android
.datamodel
;
23 import java
.lang
.ref
.WeakReference
;
25 import org
.apache
.commons
.httpclient
.HttpStatus
;
26 import org
.apache
.commons
.httpclient
.methods
.GetMethod
;
28 import android
.accounts
.Account
;
29 import android
.accounts
.AccountManager
;
30 import android
.content
.res
.Resources
;
31 import android
.graphics
.Bitmap
;
32 import android
.graphics
.Bitmap
.CompressFormat
;
33 import android
.graphics
.BitmapFactory
;
34 import android
.graphics
.drawable
.BitmapDrawable
;
35 import android
.graphics
.drawable
.Drawable
;
36 import android
.media
.ThumbnailUtils
;
37 import android
.net
.Uri
;
38 import android
.os
.AsyncTask
;
39 import android
.widget
.ImageView
;
41 import com
.owncloud
.android
.MainApp
;
42 import com
.owncloud
.android
.R
;
43 import com
.owncloud
.android
.lib
.common
.OwnCloudAccount
;
44 import com
.owncloud
.android
.lib
.common
.OwnCloudClient
;
45 import com
.owncloud
.android
.lib
.common
.OwnCloudClientManagerFactory
;
46 import com
.owncloud
.android
.lib
.common
.accounts
.AccountUtils
.Constants
;
47 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
;
48 import com
.owncloud
.android
.lib
.resources
.status
.OwnCloudVersion
;
49 import com
.owncloud
.android
.ui
.adapter
.DiskLruImageCache
;
50 import com
.owncloud
.android
.utils
.BitmapUtils
;
51 import com
.owncloud
.android
.utils
.DisplayUtils
;
54 * Manager for concurrent access to thumbnails cache.
56 public class ThumbnailsCacheManager
{
58 private static final String TAG
= ThumbnailsCacheManager
.class.getSimpleName();
60 private static final String CACHE_FOLDER
= "thumbnailCache";
61 private static final String MINOR_SERVER_VERSION_FOR_THUMBS
= "7.8.0";
63 private static final Object mThumbnailsDiskCacheLock
= new Object();
64 private static DiskLruImageCache mThumbnailCache
= null
;
65 private static boolean mThumbnailCacheStarting
= true
;
67 private static final int DISK_CACHE_SIZE
= 1024 * 1024 * 10; // 10MB
68 private static final CompressFormat mCompressFormat
= CompressFormat
.JPEG
;
69 private static final int mCompressQuality
= 70;
70 private static OwnCloudClient mClient
= null
;
71 private static String mServerVersion
= null
;
73 public static Bitmap mDefaultImg
=
74 BitmapFactory
.decodeResource(
75 MainApp
.getAppContext().getResources(),
76 DisplayUtils
.getFileTypeIconId("image/png", "default.png")
80 public static class InitDiskCacheTask
extends AsyncTask
<File
, Void
, Void
> {
83 protected Void
doInBackground(File
... params
) {
84 synchronized (mThumbnailsDiskCacheLock
) {
85 mThumbnailCacheStarting
= true
;
87 if (mThumbnailCache
== null
) {
89 // Check if media is mounted or storage is built-in, if so,
90 // try and use external cache dir; otherwise use internal cache dir
91 final String cachePath
=
92 MainApp
.getAppContext().getExternalCacheDir().getPath() +
93 File
.separator
+ CACHE_FOLDER
;
94 Log_OC
.d(TAG
, "create dir: " + cachePath
);
95 final File diskCacheDir
= new File(cachePath
);
96 mThumbnailCache
= new DiskLruImageCache(
102 } catch (Exception e
) {
103 Log_OC
.d(TAG
, "Thumbnail cache could not be opened ", e
);
104 mThumbnailCache
= null
;
107 mThumbnailCacheStarting
= false
; // Finished initialization
108 mThumbnailsDiskCacheLock
.notifyAll(); // Wake any waiting threads
115 public static void addBitmapToCache(String key
, Bitmap bitmap
) {
116 synchronized (mThumbnailsDiskCacheLock
) {
117 if (mThumbnailCache
!= null
) {
118 mThumbnailCache
.put(key
, bitmap
);
124 public static Bitmap
getBitmapFromDiskCache(String key
) {
125 synchronized (mThumbnailsDiskCacheLock
) {
126 // Wait while disk cache is started from background thread
127 while (mThumbnailCacheStarting
) {
129 mThumbnailsDiskCacheLock
.wait();
130 } catch (InterruptedException e
) {}
132 if (mThumbnailCache
!= null
) {
133 return (Bitmap
) mThumbnailCache
.getBitmap(key
);
139 public static class ThumbnailGenerationTask
extends AsyncTask
<Object
, Void
, Bitmap
> {
140 private final WeakReference
<ImageView
> mImageViewReference
;
141 private static Account mAccount
;
142 private Object mFile
;
143 private FileDataStorageManager mStorageManager
;
146 public ThumbnailGenerationTask(ImageView imageView
, FileDataStorageManager storageManager
, Account account
) {
147 // Use a WeakReference to ensure the ImageView can be garbage collected
148 mImageViewReference
= new WeakReference
<ImageView
>(imageView
);
149 if (storageManager
== null
)
150 throw new IllegalArgumentException("storageManager must not be NULL");
151 mStorageManager
= storageManager
;
155 public ThumbnailGenerationTask(ImageView imageView
) {
156 // Use a WeakReference to ensure the ImageView can be garbage collected
157 mImageViewReference
= new WeakReference
<ImageView
>(imageView
);
161 protected Bitmap
doInBackground(Object
... params
) {
162 Bitmap thumbnail
= null
;
165 if (mAccount
!= null
) {
166 AccountManager accountMgr
= AccountManager
.get(MainApp
.getAppContext());
168 mServerVersion
= accountMgr
.getUserData(mAccount
, Constants
.KEY_OC_VERSION
);
169 OwnCloudAccount ocAccount
= new OwnCloudAccount(mAccount
, MainApp
.getAppContext());
170 mClient
= OwnCloudClientManagerFactory
.getDefaultSingleton().
171 getClientFor(ocAccount
, MainApp
.getAppContext());
176 if (mFile
instanceof OCFile
) {
177 thumbnail
= doOCFileInBackground();
178 } else if (mFile
instanceof File
) {
179 thumbnail
= doFileInBackground();
185 // the app should never break due to a problem with thumbnails
186 Log_OC
.e(TAG
, "Generation of thumbnail for " + mFile
+ " failed", t
);
187 if (t
instanceof OutOfMemoryError
) {
195 protected void onPostExecute(Bitmap bitmap
){
200 if (mImageViewReference
!= null
&& bitmap
!= null
) {
201 final ImageView imageView
= mImageViewReference
.get();
202 final ThumbnailGenerationTask bitmapWorkerTask
= getBitmapWorkerTask(imageView
);
203 if (this == bitmapWorkerTask
&& imageView
!= null
) {
205 if (mFile
instanceof OCFile
){
206 tagId
= String
.valueOf(((OCFile
)mFile
).getFileId());
207 } else if (mFile
instanceof File
){
208 tagId
= String
.valueOf(((File
)mFile
).hashCode());
210 if (String
.valueOf(imageView
.getTag()).equals(tagId
)) {
211 imageView
.setImageBitmap(bitmap
);
218 * Add thumbnail to cache
219 * @param imageKey: thumb key
220 * @param bitmap: image for extracting thumbnail
221 * @param path: image path
222 * @param px: thumbnail dp
225 private Bitmap
addThumbnailToCache(String imageKey
, Bitmap bitmap
, String path
, int px
){
227 Bitmap thumbnail
= ThumbnailUtils
.extractThumbnail(bitmap
, px
, px
);
229 // Rotate image, obeying exif tag
230 thumbnail
= BitmapUtils
.rotateImage(thumbnail
,path
);
232 // Add thumbnail to cache
233 addBitmapToCache(imageKey
, thumbnail
);
239 * Converts size of file icon from dp to pixel
242 private int getThumbnailDimension(){
243 // Converts dp to pixel
244 Resources r
= MainApp
.getAppContext().getResources();
245 return (int) Math
.round(r
.getDimension(R
.dimen
.file_icon_size_grid
));
248 private Bitmap
doOCFileInBackground() {
249 Bitmap thumbnail
= null
;
250 OCFile file
= (OCFile
)mFile
;
252 final String imageKey
= String
.valueOf(file
.getRemoteId());
254 // Check disk cache in background thread
255 thumbnail
= getBitmapFromDiskCache(imageKey
);
257 // Not found in disk cache
258 if (thumbnail
== null
|| file
.needsUpdateThumbnail()) {
260 int px
= getThumbnailDimension();
263 Bitmap bitmap
= BitmapUtils
.decodeSampledBitmapFromFile(
264 file
.getStoragePath(), px
, px
);
266 if (bitmap
!= null
) {
267 thumbnail
= addThumbnailToCache(imageKey
, bitmap
, file
.getStoragePath(), px
);
269 file
.setNeedsUpdateThumbnail(false
);
270 mStorageManager
.saveFile(file
);
274 // Download thumbnail from server
275 if (mClient
!= null
&& mServerVersion
!= null
) {
276 OwnCloudVersion serverOCVersion
= new OwnCloudVersion(mServerVersion
);
277 if (serverOCVersion
.compareTo(new OwnCloudVersion(MINOR_SERVER_VERSION_FOR_THUMBS
)) >= 0) {
281 String uri
= mClient
.getBaseUri() + "/index.php/apps/files/api/v1/thumbnail/" +
282 px
+ "/" + px
+ Uri
.encode(file
.getRemotePath(), "/");
283 Log_OC
.d("Thumbnail", "URI: " + uri
);
284 GetMethod get
= new GetMethod(uri
);
285 status
= mClient
.executeMethod(get
);
286 if (status
== HttpStatus
.SC_OK
) {
287 byte[] bytes
= get
.getResponseBody();
288 Bitmap bitmap
= BitmapFactory
.decodeByteArray(bytes
, 0, bytes
.length
);
289 thumbnail
= ThumbnailUtils
.extractThumbnail(bitmap
, px
, px
);
291 // Add thumbnail to cache
292 if (thumbnail
!= null
) {
293 addBitmapToCache(imageKey
, thumbnail
);
296 } catch (Exception e
) {
300 Log_OC
.d(TAG
, "Server too old");
310 private Bitmap
doFileInBackground() {
311 Bitmap thumbnail
= null
;
312 File file
= (File
)mFile
;
314 final String imageKey
= String
.valueOf(file
.hashCode());
316 // Check disk cache in background thread
317 thumbnail
= getBitmapFromDiskCache(imageKey
);
319 // Not found in disk cache
320 if (thumbnail
== null
) {
322 int px
= getThumbnailDimension();
324 Bitmap bitmap
= BitmapUtils
.decodeSampledBitmapFromFile(
325 file
.getAbsolutePath(), px
, px
);
327 if (bitmap
!= null
) {
328 thumbnail
= addThumbnailToCache(imageKey
, bitmap
, file
.getPath(), px
);
336 public static boolean cancelPotentialWork(Object file
, ImageView imageView
) {
337 final ThumbnailGenerationTask bitmapWorkerTask
= getBitmapWorkerTask(imageView
);
339 if (bitmapWorkerTask
!= null
) {
340 final Object bitmapData
= bitmapWorkerTask
.mFile
;
341 // If bitmapData is not yet set or it differs from the new data
342 if (bitmapData
== null
|| bitmapData
!= file
) {
343 // Cancel previous task
344 bitmapWorkerTask
.cancel(true
);
346 // The same work is already in progress
350 // No task associated with the ImageView, or an existing task was cancelled
354 public static ThumbnailGenerationTask
getBitmapWorkerTask(ImageView imageView
) {
355 if (imageView
!= null
) {
356 final Drawable drawable
= imageView
.getDrawable();
357 if (drawable
instanceof AsyncDrawable
) {
358 final AsyncDrawable asyncDrawable
= (AsyncDrawable
) drawable
;
359 return asyncDrawable
.getBitmapWorkerTask();
365 public static class AsyncDrawable
extends BitmapDrawable
{
366 private final WeakReference
<ThumbnailGenerationTask
> bitmapWorkerTaskReference
;
368 public AsyncDrawable(
369 Resources res
, Bitmap bitmap
, ThumbnailGenerationTask bitmapWorkerTask
373 bitmapWorkerTaskReference
=
374 new WeakReference
<ThumbnailGenerationTask
>(bitmapWorkerTask
);
377 public ThumbnailGenerationTask
getBitmapWorkerTask() {
378 return bitmapWorkerTaskReference
.get();