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