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