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();