2cedcb41db644914687d4877bf6e2def20b840da
[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.io.IOException;
22 import java.lang.ref.WeakReference;
23
24 import org.apache.commons.httpclient.HttpStatus;
25 import org.apache.commons.httpclient.methods.GetMethod;
26
27 import android.accounts.Account;
28 import android.accounts.AuthenticatorException;
29 import android.accounts.OperationCanceledException;
30 import android.content.Context;
31 import android.content.res.Resources;
32 import android.graphics.Bitmap;
33 import android.graphics.Bitmap.CompressFormat;
34 import android.graphics.BitmapFactory;
35 import android.graphics.drawable.BitmapDrawable;
36 import android.graphics.drawable.Drawable;
37 import android.media.ThumbnailUtils;
38 import android.net.Uri;
39 import android.os.AsyncTask;
40 import android.util.TypedValue;
41 import android.widget.ImageView;
42
43 import com.owncloud.android.MainApp;
44 import com.owncloud.android.lib.common.OwnCloudAccount;
45 import com.owncloud.android.lib.common.OwnCloudClient;
46 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
47 import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException;
48 import com.owncloud.android.lib.common.utils.Log_OC;
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 * @author Tobias Kaminsky
57 * @author David A. Velasco
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
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;
73
74 public static Bitmap mDefaultImg =
75 BitmapFactory.decodeResource(
76 MainApp.getAppContext().getResources(),
77 DisplayUtils.getResourceId("image/png", "default.png")
78 );
79
80
81 public static class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
82 private static Account mAccount;
83 private static Context mContext;
84
85 public InitDiskCacheTask(Account account, Context context) {
86 mAccount = account;
87 mContext = context;
88 }
89
90 @Override
91 protected Void doInBackground(File... params) {
92 synchronized (mThumbnailsDiskCacheLock) {
93 mThumbnailCacheStarting = true;
94
95 if (mThumbnailCache == null) {
96 try {
97 OwnCloudAccount ocAccount;
98 try {
99 ocAccount = new OwnCloudAccount(mAccount, mContext);
100 mClient = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, mContext);
101 } catch (AccountNotFoundException e) {
102 // TODO Auto-generated catch block
103 e.printStackTrace();
104 } catch (AuthenticatorException e) {
105 // TODO Auto-generated catch block
106 e.printStackTrace();
107 } catch (OperationCanceledException e) {
108 // TODO Auto-generated catch block
109 e.printStackTrace();
110 } catch (IOException e) {
111 // TODO Auto-generated catch block
112 e.printStackTrace();
113 }
114
115 // Check if media is mounted or storage is built-in, if so,
116 // try and use external cache dir; otherwise use internal cache dir
117 final String cachePath =
118 MainApp.getAppContext().getExternalCacheDir().getPath() +
119 File.separator + CACHE_FOLDER;
120 Log_OC.d(TAG, "create dir: " + cachePath);
121 final File diskCacheDir = new File(cachePath);
122 mThumbnailCache = new DiskLruImageCache(
123 diskCacheDir,
124 DISK_CACHE_SIZE,
125 mCompressFormat,
126 mCompressQuality
127 );
128 } catch (Exception e) {
129 Log_OC.d(TAG, "Thumbnail cache could not be opened ", e);
130 mThumbnailCache = null;
131 }
132 }
133 mThumbnailCacheStarting = false; // Finished initialization
134 mThumbnailsDiskCacheLock.notifyAll(); // Wake any waiting threads
135 }
136 return null;
137 }
138 }
139
140
141 public static void addBitmapToCache(String key, Bitmap bitmap) {
142 synchronized (mThumbnailsDiskCacheLock) {
143 if (mThumbnailCache != null) {
144 mThumbnailCache.put(key, bitmap);
145 }
146 }
147 }
148
149
150 public static Bitmap getBitmapFromDiskCache(String key) {
151 synchronized (mThumbnailsDiskCacheLock) {
152 // Wait while disk cache is started from background thread
153 while (mThumbnailCacheStarting) {
154 try {
155 mThumbnailsDiskCacheLock.wait();
156 } catch (InterruptedException e) {}
157 }
158 if (mThumbnailCache != null) {
159 return (Bitmap) mThumbnailCache.getBitmap(key);
160 }
161 }
162 return null;
163 }
164
165
166 public static boolean cancelPotentialWork(OCFile file, ImageView imageView) {
167 final ThumbnailGenerationTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
168
169 if (bitmapWorkerTask != null) {
170 final OCFile bitmapData = bitmapWorkerTask.mFile;
171 // If bitmapData is not yet set or it differs from the new data
172 if (bitmapData == null || bitmapData != file) {
173 // Cancel previous task
174 bitmapWorkerTask.cancel(true);
175 } else {
176 // The same work is already in progress
177 return false;
178 }
179 }
180 // No task associated with the ImageView, or an existing task was cancelled
181 return true;
182 }
183
184 public static ThumbnailGenerationTask getBitmapWorkerTask(ImageView imageView) {
185 if (imageView != null) {
186 final Drawable drawable = imageView.getDrawable();
187 if (drawable instanceof AsyncDrawable) {
188 final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
189 return asyncDrawable.getBitmapWorkerTask();
190 }
191 }
192 return null;
193 }
194
195 public static class ThumbnailGenerationTask extends AsyncTask<OCFile, Void, Bitmap> {
196 private final WeakReference<ImageView> mImageViewReference;
197 private OCFile mFile;
198 private FileDataStorageManager mStorageManager;
199
200 public ThumbnailGenerationTask(ImageView imageView, FileDataStorageManager storageManager) {
201 // Use a WeakReference to ensure the ImageView can be garbage collected
202 mImageViewReference = new WeakReference<ImageView>(imageView);
203 if (storageManager == null)
204 throw new IllegalArgumentException("storageManager must not be NULL");
205 mStorageManager = storageManager;
206 }
207
208 // Decode image in background.
209 @Override
210 protected Bitmap doInBackground(OCFile... params) {
211 Bitmap thumbnail = null;
212
213 try {
214 mFile = params[0];
215 final String imageKey = String.valueOf(mFile.getRemoteId());
216
217 // Check disk cache in background thread
218 thumbnail = getBitmapFromDiskCache(imageKey);
219
220 // Not found in disk cache
221 if (thumbnail == null || mFile.needsUpdateThumbnail()) {
222 // Converts dp to pixel
223 Resources r = MainApp.getAppContext().getResources();
224 int px = (int) Math.round(TypedValue.applyDimension(
225 TypedValue.COMPLEX_UNIT_DIP, 150, r.getDisplayMetrics()
226 ));
227
228 if (mFile.isDown()){
229 Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(
230 mFile.getStoragePath(), px, px);
231
232 if (bitmap != null) {
233 thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
234
235 // Add thumbnail to cache
236 addBitmapToCache(imageKey, thumbnail);
237
238 mFile.setNeedsUpdateThumbnail(false);
239 mStorageManager.saveFile(mFile);
240 }
241
242 } else {
243 // Download thumbnail from server
244 try {
245 int status = -1;
246
247 String uri = mClient.getBaseUri() + "/index.php/apps/files/api/v1/thumbnail/" + px + "/" + px
248 + Uri.encode(mFile.getRemotePath(), "/");
249 Log_OC.d("Thumbnail", "URI: " + uri);
250 GetMethod get = new GetMethod(uri);
251 status = mClient.executeMethod(get);
252 if (status == HttpStatus.SC_OK) {
253 byte[] bytes = get.getResponseBody();
254 Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
255 thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
256
257 // Add thumbnail to cache
258 if (thumbnail != null) {
259 addBitmapToCache(imageKey, thumbnail);
260 }
261 }
262 } catch (Exception e) {
263 e.printStackTrace();
264 }
265 }
266 }
267
268 } catch (Throwable t) {
269 // the app should never break due to a problem with thumbnails
270 Log_OC.e(TAG, "Generation of thumbnail for " + mFile + " failed", t);
271 if (t instanceof OutOfMemoryError) {
272 System.gc();
273 }
274 }
275
276 return thumbnail;
277 }
278
279 protected void onPostExecute(Bitmap bitmap){
280 if (isCancelled()) {
281 bitmap = null;
282 }
283
284 if (mImageViewReference != null && bitmap != null) {
285 final ImageView imageView = mImageViewReference.get();
286 final ThumbnailGenerationTask bitmapWorkerTask =
287 getBitmapWorkerTask(imageView);
288 if (this == bitmapWorkerTask && imageView != null) {
289 if (imageView.getTag().equals(mFile.getFileId())) {
290 imageView.setImageBitmap(bitmap);
291 }
292 }
293 }
294 }
295 }
296
297
298 public static class AsyncDrawable extends BitmapDrawable {
299 private final WeakReference<ThumbnailGenerationTask> bitmapWorkerTaskReference;
300
301 public AsyncDrawable(
302 Resources res, Bitmap bitmap, ThumbnailGenerationTask bitmapWorkerTask
303 ) {
304
305 super(res, bitmap);
306 bitmapWorkerTaskReference =
307 new WeakReference<ThumbnailGenerationTask>(bitmapWorkerTask);
308 }
309
310 public ThumbnailGenerationTask getBitmapWorkerTask() {
311 return bitmapWorkerTaskReference.get();
312 }
313 }
314
315
316 /**
317 * Remove from cache the remoteId passed
318 * @param fileRemoteId: remote id of mFile passed
319 */
320 public static void removeFileFromCache(String fileRemoteId){
321 synchronized (mThumbnailsDiskCacheLock) {
322 if (mThumbnailCache != null) {
323 mThumbnailCache.removeKey(fileRemoteId);
324 }
325 mThumbnailsDiskCacheLock.notifyAll(); // Wake any waiting threads
326 }
327 }
328
329 }