05d9fe99f76b43bd08dd9583cc25cfe7e113fcd1
[pub/Android/ownCloud.git] / src / com / owncloud / android / datamodel / ThumbnailsCacheManager.java
1 /* ownCloud Android client application
2 * @author Tobias Kaminsky
3 * @author David A. Velasco
4 * Copyright (C) 2012-2014 ownCloud Inc.
5 *
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.
9 *
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.
14 *
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/>.
17 *
18 */
19
20 package com.owncloud.android.datamodel;
21
22 import java.io.File;
23 import java.lang.ref.WeakReference;
24
25 import org.apache.commons.httpclient.HttpStatus;
26 import org.apache.commons.httpclient.methods.GetMethod;
27
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;
40
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;
52
53 /**
54 * Manager for concurrent access to thumbnails cache.
55 */
56 public class ThumbnailsCacheManager {
57
58 private static final String TAG = ThumbnailsCacheManager.class.getSimpleName();
59
60 private static final String CACHE_FOLDER = "thumbnailCache";
61 private static final String MINOR_SERVER_VERSION_FOR_THUMBS = "7.8.0";
62
63 private static final Object mThumbnailsDiskCacheLock = new Object();
64 private static DiskLruImageCache mThumbnailCache = null;
65 private static boolean mThumbnailCacheStarting = true;
66
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;
72
73 public static Bitmap mDefaultImg =
74 BitmapFactory.decodeResource(
75 MainApp.getAppContext().getResources(),
76 DisplayUtils.getFileTypeIconId("image/png", "default.png")
77 );
78
79
80 public static class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
81
82 @Override
83 protected Void doInBackground(File... params) {
84 synchronized (mThumbnailsDiskCacheLock) {
85 mThumbnailCacheStarting = true;
86
87 if (mThumbnailCache == null) {
88 try {
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(
97 diskCacheDir,
98 DISK_CACHE_SIZE,
99 mCompressFormat,
100 mCompressQuality
101 );
102 } catch (Exception e) {
103 Log_OC.d(TAG, "Thumbnail cache could not be opened ", e);
104 mThumbnailCache = null;
105 }
106 }
107 mThumbnailCacheStarting = false; // Finished initialization
108 mThumbnailsDiskCacheLock.notifyAll(); // Wake any waiting threads
109 }
110 return null;
111 }
112 }
113
114
115 public static void addBitmapToCache(String key, Bitmap bitmap) {
116 synchronized (mThumbnailsDiskCacheLock) {
117 if (mThumbnailCache != null) {
118 mThumbnailCache.put(key, bitmap);
119 }
120 }
121 }
122
123
124 public static Bitmap getBitmapFromDiskCache(String key) {
125 synchronized (mThumbnailsDiskCacheLock) {
126 // Wait while disk cache is started from background thread
127 while (mThumbnailCacheStarting) {
128 try {
129 mThumbnailsDiskCacheLock.wait();
130 } catch (InterruptedException e) {}
131 }
132 if (mThumbnailCache != null) {
133 return (Bitmap) mThumbnailCache.getBitmap(key);
134 }
135 }
136 return null;
137 }
138
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;
144
145
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;
152 mAccount = account;
153 }
154
155 public ThumbnailGenerationTask(ImageView imageView) {
156 // Use a WeakReference to ensure the ImageView can be garbage collected
157 mImageViewReference = new WeakReference<ImageView>(imageView);
158 }
159
160 @Override
161 protected Bitmap doInBackground(Object... params) {
162 Bitmap thumbnail = null;
163
164 try {
165 if (mAccount != null) {
166 AccountManager accountMgr = AccountManager.get(MainApp.getAppContext());
167
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());
172 }
173
174 mFile = params[0];
175
176 if (mFile instanceof OCFile) {
177 thumbnail = doOCFileInBackground();
178 } else if (mFile instanceof File) {
179 thumbnail = doFileInBackground();
180 } else {
181 // do nothing
182 }
183
184 }catch(Throwable t){
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) {
188 System.gc();
189 }
190 }
191
192 return thumbnail;
193 }
194
195 protected void onPostExecute(Bitmap bitmap){
196 if (isCancelled()) {
197 bitmap = null;
198 }
199
200 if (mImageViewReference != null && bitmap != null) {
201 final ImageView imageView = mImageViewReference.get();
202 final ThumbnailGenerationTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
203 if (this == bitmapWorkerTask && imageView != null) {
204 String tagId = "";
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());
209 }
210 if (String.valueOf(imageView.getTag()).equals(tagId)) {
211 imageView.setImageBitmap(bitmap);
212 }
213 }
214 }
215 }
216
217 /**
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
223 * @return Bitmap
224 */
225 private Bitmap addThumbnailToCache(String imageKey, Bitmap bitmap, String path, int px){
226
227 Bitmap thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
228
229 // Rotate image, obeying exif tag
230 thumbnail = BitmapUtils.rotateImage(thumbnail,path);
231
232 // Add thumbnail to cache
233 addBitmapToCache(imageKey, thumbnail);
234
235 return thumbnail;
236 }
237
238 /**
239 * Converts size of file icon from dp to pixel
240 * @return int
241 */
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));
246 }
247
248 private Bitmap doOCFileInBackground() {
249 Bitmap thumbnail = null;
250 OCFile file = (OCFile)mFile;
251
252 final String imageKey = String.valueOf(file.getRemoteId());
253
254 // Check disk cache in background thread
255 thumbnail = getBitmapFromDiskCache(imageKey);
256
257 // Not found in disk cache
258 if (thumbnail == null || file.needsUpdateThumbnail()) {
259
260 int px = getThumbnailDimension();
261
262 if (file.isDown()) {
263 Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(
264 file.getStoragePath(), px, px);
265
266 if (bitmap != null) {
267 thumbnail = addThumbnailToCache(imageKey, bitmap, file.getStoragePath(), px);
268
269 file.setNeedsUpdateThumbnail(false);
270 mStorageManager.saveFile(file);
271 }
272
273 } else {
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) {
278 try {
279 int status = -1;
280
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);
290
291 // Add thumbnail to cache
292 if (thumbnail != null) {
293 addBitmapToCache(imageKey, thumbnail);
294 }
295 }
296 } catch (Exception e) {
297 e.printStackTrace();
298 }
299 } else {
300 Log_OC.d(TAG, "Server too old");
301 }
302 }
303 }
304 }
305
306 return thumbnail;
307
308 }
309
310 private Bitmap doFileInBackground() {
311 Bitmap thumbnail = null;
312 File file = (File)mFile;
313
314 final String imageKey = String.valueOf(file.hashCode());
315
316 // Check disk cache in background thread
317 thumbnail = getBitmapFromDiskCache(imageKey);
318
319 // Not found in disk cache
320 if (thumbnail == null) {
321
322 int px = getThumbnailDimension();
323
324 Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(
325 file.getAbsolutePath(), px, px);
326
327 if (bitmap != null) {
328 thumbnail = addThumbnailToCache(imageKey, bitmap, file.getPath(), px);
329 }
330 }
331 return thumbnail;
332 }
333
334 }
335
336 public static boolean cancelPotentialWork(Object file, ImageView imageView) {
337 final ThumbnailGenerationTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
338
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);
345 } else {
346 // The same work is already in progress
347 return false;
348 }
349 }
350 // No task associated with the ImageView, or an existing task was cancelled
351 return true;
352 }
353
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();
360 }
361 }
362 return null;
363 }
364
365 public static class AsyncDrawable extends BitmapDrawable {
366 private final WeakReference<ThumbnailGenerationTask> bitmapWorkerTaskReference;
367
368 public AsyncDrawable(
369 Resources res, Bitmap bitmap, ThumbnailGenerationTask bitmapWorkerTask
370 ) {
371
372 super(res, bitmap);
373 bitmapWorkerTaskReference =
374 new WeakReference<ThumbnailGenerationTask>(bitmapWorkerTask);
375 }
376
377 public ThumbnailGenerationTask getBitmapWorkerTask() {
378 return bitmapWorkerTaskReference.get();
379 }
380 }
381 }