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