eeb01829d0e0df06905d17bfa34e660014311ed8
[pub/Android/ownCloud.git] / src / com / owncloud / android / datamodel / ThumbnailsCacheManager.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012-2014 ownCloud Inc.
3 *
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.
7 *
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.
12 *
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/>.
15 *
16 */
17
18 package com.owncloud.android.datamodel;
19
20 import java.io.File;
21 import java.lang.ref.WeakReference;
22
23 import org.apache.commons.httpclient.HttpStatus;
24 import org.apache.commons.httpclient.methods.GetMethod;
25
26 import android.accounts.Account;
27 import android.accounts.AccountManager;
28 import android.content.res.Resources;
29 import android.graphics.Bitmap;
30 import android.graphics.Bitmap.CompressFormat;
31 import android.graphics.BitmapFactory;
32 import android.graphics.drawable.BitmapDrawable;
33 import android.graphics.drawable.Drawable;
34 import android.media.ThumbnailUtils;
35 import android.net.Uri;
36 import android.os.AsyncTask;
37 import android.widget.ImageView;
38
39 import com.owncloud.android.MainApp;
40 import com.owncloud.android.R;
41 import com.owncloud.android.lib.common.OwnCloudAccount;
42 import com.owncloud.android.lib.common.OwnCloudClient;
43 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
44 import com.owncloud.android.lib.common.accounts.AccountUtils.Constants;
45 import com.owncloud.android.lib.common.utils.Log_OC;
46 import com.owncloud.android.lib.resources.status.OwnCloudVersion;
47 import com.owncloud.android.ui.adapter.DiskLruImageCache;
48 import com.owncloud.android.utils.BitmapUtils;
49 import com.owncloud.android.utils.DisplayUtils;
50
51 /**
52 * Manager for concurrent access to thumbnails cache.
53 *
54 * @author Tobias Kaminsky
55 * @author David A. Velasco
56 */
57 public class ThumbnailsCacheManager {
58
59 private static final String TAG = ThumbnailsCacheManager.class.getSimpleName();
60
61 private static final String CACHE_FOLDER = "thumbnailCache";
62 private static final String MINOR_SERVER_VERSION_FOR_THUMBS = "7.8.0";
63
64 private static final Object mThumbnailsDiskCacheLock = new Object();
65 private static DiskLruImageCache mThumbnailCache = null;
66 private static boolean mThumbnailCacheStarting = true;
67
68 private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
69 private static final CompressFormat mCompressFormat = CompressFormat.JPEG;
70 private static final int mCompressQuality = 70;
71 private static OwnCloudClient mClient = null;
72 private static String mServerVersion = null;
73
74 public static Bitmap mDefaultImg =
75 BitmapFactory.decodeResource(
76 MainApp.getAppContext().getResources(),
77 DisplayUtils.getFileTypeIconId("image/png", "default.png")
78 );
79
80
81 public static class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
82
83 @Override
84 protected Void doInBackground(File... params) {
85 synchronized (mThumbnailsDiskCacheLock) {
86 mThumbnailCacheStarting = true;
87
88 if (mThumbnailCache == null) {
89 try {
90 // Check if media is mounted or storage is built-in, if so,
91 // try and use external cache dir; otherwise use internal cache dir
92 final String cachePath =
93 MainApp.getAppContext().getExternalCacheDir().getPath() +
94 File.separator + CACHE_FOLDER;
95 Log_OC.d(TAG, "create dir: " + cachePath);
96 final File diskCacheDir = new File(cachePath);
97 mThumbnailCache = new DiskLruImageCache(
98 diskCacheDir,
99 DISK_CACHE_SIZE,
100 mCompressFormat,
101 mCompressQuality
102 );
103 } catch (Exception e) {
104 Log_OC.d(TAG, "Thumbnail cache could not be opened ", e);
105 mThumbnailCache = null;
106 }
107 }
108 mThumbnailCacheStarting = false; // Finished initialization
109 mThumbnailsDiskCacheLock.notifyAll(); // Wake any waiting threads
110 }
111 return null;
112 }
113 }
114
115
116 public static void addBitmapToCache(String key, Bitmap bitmap) {
117 synchronized (mThumbnailsDiskCacheLock) {
118 if (mThumbnailCache != null) {
119 mThumbnailCache.put(key, bitmap);
120 }
121 }
122 }
123
124
125 public static Bitmap getBitmapFromDiskCache(String key) {
126 synchronized (mThumbnailsDiskCacheLock) {
127 // Wait while disk cache is started from background thread
128 while (mThumbnailCacheStarting) {
129 try {
130 mThumbnailsDiskCacheLock.wait();
131 } catch (InterruptedException e) {}
132 }
133 if (mThumbnailCache != null) {
134 return (Bitmap) mThumbnailCache.getBitmap(key);
135 }
136 }
137 return null;
138 }
139
140 public static class ThumbnailGenerationTask extends AsyncTask<Object, Void, Bitmap> {
141 private final WeakReference<ImageView> mImageViewReference;
142 private static Account mAccount;
143 private Object mFile;
144 private FileDataStorageManager mStorageManager;
145
146
147 public ThumbnailGenerationTask(ImageView imageView, FileDataStorageManager storageManager, Account account) {
148 // Use a WeakReference to ensure the ImageView can be garbage collected
149 mImageViewReference = new WeakReference<ImageView>(imageView);
150 if (storageManager == null)
151 throw new IllegalArgumentException("storageManager must not be NULL");
152 mStorageManager = storageManager;
153 mAccount = account;
154 }
155
156 public ThumbnailGenerationTask(ImageView imageView) {
157 // Use a WeakReference to ensure the ImageView can be garbage collected
158 mImageViewReference = new WeakReference<ImageView>(imageView);
159 }
160
161 @Override
162 protected Bitmap doInBackground(Object... params) {
163 Bitmap thumbnail = null;
164
165 try {
166 if (mAccount != null) {
167 AccountManager accountMgr = AccountManager.get(MainApp.getAppContext());
168
169 mServerVersion = accountMgr.getUserData(mAccount, Constants.KEY_OC_VERSION);
170 OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, MainApp.getAppContext());
171 mClient = OwnCloudClientManagerFactory.getDefaultSingleton().
172 getClientFor(ocAccount, MainApp.getAppContext());
173 }
174
175 mFile = params[0];
176 final String imageKey = String.valueOf(mFile.getRemoteId());
177
178 // Check disk cache in background thread
179 thumbnail = getBitmapFromDiskCache(imageKey);
180
181 // Not found in disk cache
182 if (thumbnail == null || mFile.needsUpdateThumbnail()) {
183 // Use Width of imageView -> no blurry images on big screens
184 int px = mImageViewReference.get().getWidth();
185
186 if (mFile.isDown()){
187 Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(
188 mFile.getStoragePath(), px, px);
189
190 if (bitmap != null) {
191 thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
192
193 // Add thumbnail to cache
194 addBitmapToCache(imageKey, thumbnail);
195
196 if (mFile instanceof OCFile) {
197 thumbnail = doOCFileInBackground();
198 } else if (mFile instanceof File) {
199 thumbnail = doFileInBackground();
200 } else {
201 // do nothing
202 }
203
204 }catch(Throwable t){
205 // the app should never break due to a problem with thumbnails
206 Log_OC.e(TAG, "Generation of thumbnail for " + mFile + " failed", t);
207 if (t instanceof OutOfMemoryError) {
208 System.gc();
209 }
210 }
211
212 return thumbnail;
213 }
214
215 protected void onPostExecute(Bitmap bitmap){
216 if (isCancelled()) {
217 bitmap = null;
218 }
219
220 if (mImageViewReference != null && bitmap != null) {
221 final ImageView imageView = mImageViewReference.get();
222 final ThumbnailGenerationTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
223 if (this == bitmapWorkerTask && imageView != null) {
224 String tagId = "";
225 if (mFile instanceof OCFile){
226 tagId = String.valueOf(((OCFile)mFile).getFileId());
227 } else if (mFile instanceof File){
228 tagId = String.valueOf(((File)mFile).hashCode());
229 }
230 if (String.valueOf(imageView.getTag()).equals(tagId)) {
231 imageView.setImageBitmap(bitmap);
232 }
233 }
234 }
235 }
236
237 /**
238 * Add thumbnail to cache
239 * @param imageKey: thumb key
240 * @param bitmap: image for extracting thumbnail
241 * @param path: image path
242 * @param px: thumbnail dp
243 * @return Bitmap
244 */
245 private Bitmap addThumbnailToCache(String imageKey, Bitmap bitmap, String path, int px){
246
247 Bitmap thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
248
249 // Rotate image, obeying exif tag
250 thumbnail = BitmapUtils.rotateImage(thumbnail,path);
251
252 // Add thumbnail to cache
253 addBitmapToCache(imageKey, thumbnail);
254
255 return thumbnail;
256 }
257
258 /**
259 * Converts size of file icon from dp to pixel
260 * @return int
261 */
262 private int getThumbnailDimension(){
263 // Converts dp to pixel
264 Resources r = MainApp.getAppContext().getResources();
265 return (int) Math.round(r.getDimension(R.dimen.file_icon_size));
266 }
267
268 private Bitmap doOCFileInBackground() {
269 Bitmap thumbnail = null;
270 OCFile file = (OCFile)mFile;
271
272 final String imageKey = String.valueOf(file.getRemoteId());
273
274 // Check disk cache in background thread
275 thumbnail = getBitmapFromDiskCache(imageKey);
276
277 // Not found in disk cache
278 if (thumbnail == null || file.needsUpdateThumbnail()) {
279
280 int px = getThumbnailDimension();
281
282 if (file.isDown()) {
283 Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(
284 file.getStoragePath(), px, px);
285
286 if (bitmap != null) {
287 thumbnail = addThumbnailToCache(imageKey, bitmap, file.getStoragePath(), px);
288
289 file.setNeedsUpdateThumbnail(false);
290 mStorageManager.saveFile(file);
291 }
292
293 } else {
294 // Download thumbnail from server
295 if (mClient != null && mServerVersion != null) {
296 OwnCloudVersion serverOCVersion = new OwnCloudVersion(mServerVersion);
297 if (serverOCVersion.compareTo(new OwnCloudVersion(MINOR_SERVER_VERSION_FOR_THUMBS)) >= 0) {
298 try {
299 int status = -1;
300
301 String uri = mClient.getBaseUri() + "/index.php/apps/files/api/v1/thumbnail/" +
302 px + "/" + px + Uri.encode(file.getRemotePath(), "/");
303 Log_OC.d("Thumbnail", "URI: " + uri);
304 GetMethod get = new GetMethod(uri);
305 status = mClient.executeMethod(get);
306 if (status == HttpStatus.SC_OK) {
307 byte[] bytes = get.getResponseBody();
308 Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
309 thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
310
311 // Add thumbnail to cache
312 if (thumbnail != null) {
313 addBitmapToCache(imageKey, thumbnail);
314 }
315 }
316 } catch (Exception e) {
317 e.printStackTrace();
318 }
319 } else {
320 Log_OC.d(TAG, "Server too old");
321 }
322 }
323 }
324 }
325
326 return thumbnail;
327
328 }
329
330 private Bitmap doFileInBackground() {
331 Bitmap thumbnail = null;
332 File file = (File)mFile;
333
334 final String imageKey = String.valueOf(file.hashCode());
335
336 // Check disk cache in background thread
337 thumbnail = getBitmapFromDiskCache(imageKey);
338
339 // Not found in disk cache
340 if (thumbnail == null) {
341
342 int px = getThumbnailDimension();
343
344 Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(
345 file.getAbsolutePath(), px, px);
346
347 if (bitmap != null) {
348 thumbnail = addThumbnailToCache(imageKey, bitmap, file.getPath(), px);
349 }
350 }
351 return thumbnail;
352 }
353
354 }
355
356 public static boolean cancelPotentialWork(Object file, ImageView imageView) {
357 final ThumbnailGenerationTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
358
359 if (bitmapWorkerTask != null) {
360 final Object bitmapData = bitmapWorkerTask.mFile;
361 // If bitmapData is not yet set or it differs from the new data
362 if (bitmapData == null || bitmapData != file) {
363 // Cancel previous task
364 bitmapWorkerTask.cancel(true);
365 } else {
366 // The same work is already in progress
367 return false;
368 }
369 }
370 // No task associated with the ImageView, or an existing task was cancelled
371 return true;
372 }
373
374 public static ThumbnailGenerationTask getBitmapWorkerTask(ImageView imageView) {
375 if (imageView != null) {
376 final Drawable drawable = imageView.getDrawable();
377 if (drawable instanceof AsyncDrawable) {
378 final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
379 return asyncDrawable.getBitmapWorkerTask();
380 }
381 }
382 return null;
383 }
384
385 public static class AsyncDrawable extends BitmapDrawable {
386 private final WeakReference<ThumbnailGenerationTask> bitmapWorkerTaskReference;
387
388 public AsyncDrawable(
389 Resources res, Bitmap bitmap, ThumbnailGenerationTask bitmapWorkerTask
390 ) {
391
392 super(res, bitmap);
393 bitmapWorkerTaskReference =
394 new WeakReference<ThumbnailGenerationTask>(bitmapWorkerTask);
395 }
396
397 public ThumbnailGenerationTask getBitmapWorkerTask() {
398 return bitmapWorkerTaskReference.get();
399 }
400 }
401 }