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