ThumbnailsCacheManager.java 15 KB

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