ThumbnailsCacheManager.java 15 KB

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