From: Javier Gonzalez Date: Wed, 1 Oct 2014 09:35:50 +0000 (+0200) Subject: Merge pull request #641 from owncloud/thumbnails_for_downloaded_images X-Git-Tag: oc-android-1.7.0_signed~163 X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/commitdiff_plain/d456e2fb6c2e1f0669187d8b8fe48f36f4f6cef5?hp=ab61008c9dcdccf9e9a1ecabb9a1402916cf391b Merge pull request #641 from owncloud/thumbnails_for_downloaded_images Thumbnails for downloaded images --- diff --git a/.classpath b/.classpath index 7bc01d9a..51769745 100644 --- a/.classpath +++ b/.classpath @@ -1,9 +1,9 @@ - - + + diff --git a/libs/disklrucache-2.0.2.jar b/libs/disklrucache-2.0.2.jar new file mode 100644 index 00000000..ca7907d0 Binary files /dev/null and b/libs/disklrucache-2.0.2.jar differ diff --git a/lint.xml b/lint.xml deleted file mode 100644 index ee0eead5..00000000 --- a/lint.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/com/owncloud/android/datamodel/FileDataStorageManager.java b/src/com/owncloud/android/datamodel/FileDataStorageManager.java index 4848246f..795004a1 100644 --- a/src/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/src/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -188,6 +188,7 @@ public class FileDataStorageManager { cv.put(ProviderTableMeta.FILE_PUBLIC_LINK, file.getPublicLink()); cv.put(ProviderTableMeta.FILE_PERMISSIONS, file.getPermissions()); cv.put(ProviderTableMeta.FILE_REMOTE_ID, file.getRemoteId()); + cv.put(ProviderTableMeta.FILE_UPDATE_THUMBNAIL, file.needsUpdateThumbnail()); boolean sameRemotePath = fileExists(file.getRemotePath()); if (sameRemotePath || @@ -878,6 +879,8 @@ public class FileDataStorageManager { file.setPublicLink(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_PUBLIC_LINK))); file.setPermissions(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_PERMISSIONS))); file.setRemoteId(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_REMOTE_ID))); + file.setNeedsUpdateThumbnail(c.getInt( + c.getColumnIndex(ProviderTableMeta.FILE_UPDATE_THUMBNAIL)) == 1 ? true : false); } return file; @@ -1222,6 +1225,7 @@ public class FileDataStorageManager { cv.put(ProviderTableMeta.FILE_PUBLIC_LINK, file.getPublicLink()); cv.put(ProviderTableMeta.FILE_PERMISSIONS, file.getPermissions()); cv.put(ProviderTableMeta.FILE_REMOTE_ID, file.getRemoteId()); + cv.put(ProviderTableMeta.FILE_UPDATE_THUMBNAIL, file.needsUpdateThumbnail() ? 1 : 0); boolean existsByPath = fileExists(file.getRemotePath()); if (existsByPath || fileExists(file.getFileId())) { diff --git a/src/com/owncloud/android/datamodel/OCFile.java b/src/com/owncloud/android/datamodel/OCFile.java index 58851063..c3284fc0 100644 --- a/src/com/owncloud/android/datamodel/OCFile.java +++ b/src/com/owncloud/android/datamodel/OCFile.java @@ -68,6 +68,8 @@ public class OCFile implements Parcelable, Comparable { private String mPermissions; private String mRemoteId; + private boolean mNeedsUpdateThumbnail; + /** * Create new {@link OCFile} with given path. @@ -109,6 +111,8 @@ public class OCFile implements Parcelable, Comparable { mPublicLink = source.readString(); mPermissions = source.readString(); mRemoteId = source.readString(); + mNeedsUpdateThumbnail = source.readInt() == 0; + } @Override @@ -131,6 +135,7 @@ public class OCFile implements Parcelable, Comparable { dest.writeString(mPublicLink); dest.writeString(mPermissions); dest.writeString(mRemoteId); + dest.writeInt(mNeedsUpdateThumbnail ? 1 : 0); } /** @@ -343,6 +348,7 @@ public class OCFile implements Parcelable, Comparable { mPublicLink = null; mPermissions = null; mRemoteId = null; + mNeedsUpdateThumbnail = false; } /** @@ -408,6 +414,14 @@ public class OCFile implements Parcelable, Comparable { return mNeedsUpdating; } + public boolean needsUpdateThumbnail() { + return mNeedsUpdateThumbnail; + } + + public void setNeedsUpdateThumbnail(boolean needsUpdateThumbnail) { + this.mNeedsUpdateThumbnail = needsUpdateThumbnail; + } + public long getLastSyncDateForProperties() { return mLastSyncDateForProperties; } diff --git a/src/com/owncloud/android/db/ProviderMeta.java b/src/com/owncloud/android/db/ProviderMeta.java index 1adf6971..bc59869a 100644 --- a/src/com/owncloud/android/db/ProviderMeta.java +++ b/src/com/owncloud/android/db/ProviderMeta.java @@ -31,7 +31,7 @@ import com.owncloud.android.MainApp; public class ProviderMeta { public static final String DB_NAME = "filelist"; - public static final int DB_VERSION = 7; + public static final int DB_VERSION = 8; private ProviderMeta() { } @@ -70,6 +70,7 @@ public class ProviderMeta { public static final String FILE_PUBLIC_LINK = "public_link"; public static final String FILE_PERMISSIONS = "permissions"; public static final String FILE_REMOTE_ID = "remote_id"; + public static final String FILE_UPDATE_THUMBNAIL = "update_thumbnail"; public static final String FILE_DEFAULT_SORT_ORDER = FILE_NAME + " collate nocase asc"; diff --git a/src/com/owncloud/android/files/services/FileDownloader.java b/src/com/owncloud/android/files/services/FileDownloader.java index 2e423beb..cb0f12ce 100644 --- a/src/com/owncloud/android/files/services/FileDownloader.java +++ b/src/com/owncloud/android/files/services/FileDownloader.java @@ -391,6 +391,7 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis long syncDate = System.currentTimeMillis(); file.setLastSyncDateForProperties(syncDate); file.setLastSyncDateForData(syncDate); + file.setNeedsUpdateThumbnail(true); file.setModificationTimestamp(mCurrentDownload.getModificationTimestamp()); file.setModificationTimestampAtLastSyncForData(mCurrentDownload.getModificationTimestamp()); // file.setEtag(mCurrentDownload.getEtag()); // TODO Etag, where available diff --git a/src/com/owncloud/android/providers/FileContentProvider.java b/src/com/owncloud/android/providers/FileContentProvider.java index 8113e046..9792f3e9 100644 --- a/src/com/owncloud/android/providers/FileContentProvider.java +++ b/src/com/owncloud/android/providers/FileContentProvider.java @@ -97,6 +97,8 @@ public class FileContentProvider extends ContentProvider { ProviderTableMeta.FILE_PERMISSIONS); mFileProjectionMap.put(ProviderTableMeta.FILE_REMOTE_ID, ProviderTableMeta.FILE_REMOTE_ID); + mFileProjectionMap.put(ProviderTableMeta.FILE_UPDATE_THUMBNAIL, + ProviderTableMeta.FILE_UPDATE_THUMBNAIL); } private static final int SINGLE_FILE = 1; @@ -559,7 +561,8 @@ public class FileContentProvider extends ContentProvider { + ProviderTableMeta.FILE_SHARE_BY_LINK + " INTEGER, " + ProviderTableMeta.FILE_PUBLIC_LINK + " TEXT, " + ProviderTableMeta.FILE_PERMISSIONS + " TEXT null," - + ProviderTableMeta.FILE_REMOTE_ID + " TEXT null);" + + ProviderTableMeta.FILE_REMOTE_ID + " TEXT null," + + ProviderTableMeta.FILE_UPDATE_THUMBNAIL + " INTEGER);" //boolean ); // Create table ocshares @@ -708,6 +711,23 @@ public class FileContentProvider extends ContentProvider { } if (!upgraded) Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion); + + if (oldVersion < 8 && newVersion >= 8) { + Log_OC.i("SQL", "Entering in the #8 ADD in onUpgrade"); + db.beginTransaction(); + try { + db .execSQL("ALTER TABLE " + ProviderTableMeta.FILE_TABLE_NAME + + " ADD COLUMN " + ProviderTableMeta.FILE_UPDATE_THUMBNAIL + " INTEGER " + + " DEFAULT 0"); + + upgraded = true; + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + if (!upgraded) + Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion); } } diff --git a/src/com/owncloud/android/ui/adapter/DiskLruImageCache.java b/src/com/owncloud/android/ui/adapter/DiskLruImageCache.java new file mode 100644 index 00000000..fceaba23 --- /dev/null +++ b/src/com/owncloud/android/ui/adapter/DiskLruImageCache.java @@ -0,0 +1,189 @@ +package com.owncloud.android.ui.adapter; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Bitmap.CompressFormat; +import android.graphics.BitmapFactory; +import android.util.Log; + +import com.jakewharton.disklrucache.DiskLruCache; +import com.owncloud.android.BuildConfig; +import com.owncloud.android.utils.Log_OC; + +public class DiskLruImageCache { + + private DiskLruCache mDiskCache; + private CompressFormat mCompressFormat; + private int mCompressQuality; + private static final int CACHE_VERSION = 1; + private static final int VALUE_COUNT = 1; + private static final int IO_BUFFER_SIZE = 8 * 1024; + private static final Pattern CAPITAL_LETTERS = Pattern.compile("[A-Z]"); + + private StringBuffer mValidKeyBuffer = new StringBuffer(64); + private StringBuffer mConversionBuffer = new StringBuffer(2).append('_'); + + private static final String TAG = "DiskLruImageCache"; + + public DiskLruImageCache( Context context,String uniqueName, int diskCacheSize, + CompressFormat compressFormat, int quality ) throws IOException { + final File diskCacheDir = getDiskCacheDir(context, uniqueName ); + mDiskCache = DiskLruCache.open( + diskCacheDir, CACHE_VERSION, VALUE_COUNT, diskCacheSize + ); + mCompressFormat = compressFormat; + mCompressQuality = quality; + } + + private boolean writeBitmapToFile( Bitmap bitmap, DiskLruCache.Editor editor ) + throws IOException, FileNotFoundException { + OutputStream out = null; + try { + out = new BufferedOutputStream( editor.newOutputStream( 0 ), IO_BUFFER_SIZE ); + return bitmap.compress( mCompressFormat, mCompressQuality, out ); + } finally { + if ( out != null ) { + out.close(); + } + } + } + + private File getDiskCacheDir(Context context, String uniqueName) { + + // Check if media is mounted or storage is built-in, if so, try and use external cache dir + // otherwise use internal cache dir + final String cachePath = context.getExternalCacheDir().getPath(); + + Log_OC.d(TAG, "create dir: " + cachePath + File.separator + uniqueName); + + return new File(cachePath + File.separator + uniqueName); + } + + public void put( String key, Bitmap data ) { + + DiskLruCache.Editor editor = null; + String validKey = convertToValidKey(key); + try { + editor = mDiskCache.edit( validKey ); + if ( editor == null ) { + return; + } + + if( writeBitmapToFile( data, editor ) ) { + mDiskCache.flush(); + editor.commit(); + if ( BuildConfig.DEBUG ) { + Log.d( "cache_test_DISK_", "image put on disk cache " + validKey ); + } + } else { + editor.abort(); + if ( BuildConfig.DEBUG ) { + Log.d( "cache_test_DISK_", "ERROR on: image put on disk cache " + validKey ); + } + } + } catch (IOException e) { + if ( BuildConfig.DEBUG ) { + Log.d( "cache_test_DISK_", "ERROR on: image put on disk cache " + validKey ); + } + try { + if ( editor != null ) { + editor.abort(); + } + } catch (IOException ignored) { + } + } + + } + + public Bitmap getBitmap( String key ) { + + Bitmap bitmap = null; + DiskLruCache.Snapshot snapshot = null; + String validKey = convertToValidKey(key); + try { + + snapshot = mDiskCache.get( validKey ); + if ( snapshot == null ) { + return null; + } + final InputStream in = snapshot.getInputStream( 0 ); + if ( in != null ) { + final BufferedInputStream buffIn = + new BufferedInputStream( in, IO_BUFFER_SIZE ); + bitmap = BitmapFactory.decodeStream( buffIn ); + } + } catch ( IOException e ) { + e.printStackTrace(); + } finally { + if ( snapshot != null ) { + snapshot.close(); + } + } + + if ( BuildConfig.DEBUG ) { + Log.d("cache_test_DISK_", bitmap == null ? "not found" : "image read from disk " + validKey); + } + + return bitmap; + + } + + public boolean containsKey( String key ) { + + boolean contained = false; + DiskLruCache.Snapshot snapshot = null; + String validKey = convertToValidKey(key); + try { + snapshot = mDiskCache.get( validKey ); + contained = snapshot != null; + } catch (IOException e) { + e.printStackTrace(); + } finally { + if ( snapshot != null ) { + snapshot.close(); + } + } + + return contained; + + } + + public void clearCache() { + if ( BuildConfig.DEBUG ) { + Log.d( "cache_test_DISK_", "disk cache CLEARED"); + } + try { + mDiskCache.delete(); + } catch ( IOException e ) { + e.printStackTrace(); + } + } + + public File getCacheFolder() { + return mDiskCache.getDirectory(); + } + + private String convertToValidKey(String key) { + Matcher capitalLettersMatcher = CAPITAL_LETTERS.matcher(key); + mValidKeyBuffer.delete(0, mValidKeyBuffer.length()); + mConversionBuffer.delete(1, mConversionBuffer.length()); + + while (capitalLettersMatcher.find()) { + mConversionBuffer.replace(1, 2, capitalLettersMatcher.group(0).toLowerCase()); + capitalLettersMatcher.appendReplacement(mValidKeyBuffer, mConversionBuffer.toString()); + } + capitalLettersMatcher.appendTail(mValidKeyBuffer); + return mValidKeyBuffer.toString(); + } + +} \ No newline at end of file diff --git a/src/com/owncloud/android/ui/adapter/FileListListAdapter.java b/src/com/owncloud/android/ui/adapter/FileListListAdapter.java index 65a9c83c..d748a70c 100644 --- a/src/com/owncloud/android/ui/adapter/FileListListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FileListListAdapter.java @@ -17,10 +17,21 @@ */ package com.owncloud.android.ui.adapter; +import java.io.File; +import java.lang.ref.WeakReference; import java.util.Vector; import android.accounts.Account; import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Bitmap.CompressFormat; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.media.ThumbnailUtils; +import android.os.AsyncTask; +import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -37,7 +48,9 @@ import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; import com.owncloud.android.ui.activity.ComponentsGetter; +import com.owncloud.android.utils.BitmapUtils; import com.owncloud.android.utils.DisplayUtils; +import com.owncloud.android.utils.Log_OC; /** @@ -45,10 +58,13 @@ import com.owncloud.android.utils.DisplayUtils; * instance. * * @author Bartek Przybylski - * + * @author Tobias Kaminsky + * @author David A. Velasco */ public class FileListListAdapter extends BaseAdapter implements ListAdapter { private final static String PERMISSION_SHARED_WITH_ME = "S"; + + private static final String TAG = FileListListAdapter.class.getSimpleName(); private Context mContext; private OCFile mFile = null; @@ -59,15 +75,159 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { private Account mAccount; private ComponentsGetter mTransferServiceGetter; + private final Object thumbnailDiskCacheLock = new Object(); + private DiskLruImageCache mThumbnailCache; + private boolean mThumbnailCacheStarting = true; + private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB + private static final CompressFormat mCompressFormat = CompressFormat.JPEG; + private static final int mCompressQuality = 70; + private Bitmap defaultImg; + public FileListListAdapter( boolean justFolders, Context context, ComponentsGetter transferServiceGetter ) { + mJustFolders = justFolders; mContext = context; mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext); mTransferServiceGetter = transferServiceGetter; + defaultImg = BitmapFactory.decodeResource(mContext.getResources(), + DisplayUtils.getResourceId("image/png", "default.png")); + + // Initialise disk cache on background thread + new InitDiskCacheTask().execute(); + } + + class InitDiskCacheTask extends AsyncTask { + @Override + protected Void doInBackground(File... params) { + synchronized (thumbnailDiskCacheLock) { + try { + mThumbnailCache = new DiskLruImageCache(mContext, "thumbnailCache", + DISK_CACHE_SIZE, mCompressFormat, mCompressQuality); + } catch (Exception e) { + Log_OC.d(TAG, "Thumbnail cache could not be opened ", e); + mThumbnailCache = null; + } + mThumbnailCacheStarting = false; // Finished initialization + thumbnailDiskCacheLock.notifyAll(); // Wake any waiting threads + } + return null; + } + } + + static class AsyncDrawable extends BitmapDrawable { + private final WeakReference bitmapWorkerTaskReference; + + public AsyncDrawable(Resources res, Bitmap bitmap, + ThumbnailGenerationTask bitmapWorkerTask) { + super(res, bitmap); + bitmapWorkerTaskReference = + new WeakReference(bitmapWorkerTask); + } + + public ThumbnailGenerationTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } + } + + class ThumbnailGenerationTask extends AsyncTask { + private final WeakReference imageViewReference; + private OCFile file; + + + public ThumbnailGenerationTask(ImageView imageView) { + // Use a WeakReference to ensure the ImageView can be garbage collected + imageViewReference = new WeakReference(imageView); + } + + // Decode image in background. + @Override + protected Bitmap doInBackground(OCFile... params) { + Bitmap thumbnail = null; + + try { + file = params[0]; + final String imageKey = String.valueOf(file.getRemoteId()); + + // Check disk cache in background thread + thumbnail = getBitmapFromDiskCache(imageKey); + + // Not found in disk cache + if (thumbnail == null || file.needsUpdateThumbnail()) { + // Converts dp to pixel + Resources r = mContext.getResources(); + int px = (int) Math.round(TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, 150, r.getDisplayMetrics() + )); + + if (file.isDown()){ + Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile( + file.getStoragePath(), px, px); + + if (bitmap != null) { + thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px); + + // Add thumbnail to cache + addBitmapToCache(imageKey, thumbnail); + + file.setNeedsUpdateThumbnail(false); + mStorageManager.saveFile(file); + } + + } + } + + } catch (Throwable t) { + // the app should never break due to a problem with thumbnails + Log_OC.e(TAG, "Generation of thumbnail for " + file + " failed", t); + if (t instanceof OutOfMemoryError) { + System.gc(); + } + } + + return thumbnail; + } + + protected void onPostExecute(Bitmap bitmap){ + if (isCancelled()) { + bitmap = null; + } + + if (imageViewReference != null && bitmap != null) { + final ImageView imageView = imageViewReference.get(); + final ThumbnailGenerationTask bitmapWorkerTask = + getBitmapWorkerTask(imageView); + if (this == bitmapWorkerTask && imageView != null) { + imageView.setImageBitmap(bitmap); + } + } + } + } + + public void addBitmapToCache(String key, Bitmap bitmap) { + synchronized (thumbnailDiskCacheLock) { + if (mThumbnailCache != null) { + mThumbnailCache.put(key, bitmap); + } + } + } + + public Bitmap getBitmapFromDiskCache(String key) { + synchronized (thumbnailDiskCacheLock) { + // Wait while disk cache is started from background thread + while (mThumbnailCacheStarting) { + try { + thumbnailDiskCacheLock.wait(); + } catch (InterruptedException e) {} + } + if (mThumbnailCache != null) { + return (Bitmap) mThumbnailCache.getBitmap(key); + } + } + return null; } @Override @@ -112,10 +272,10 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { .getSystemService(Context.LAYOUT_INFLATER_SERVICE); view = inflator.inflate(R.layout.list_item, null); } - + if (mFiles != null && mFiles.size() > position) { OCFile file = mFiles.get(position); - TextView fileName = (TextView) view.findViewById(R.id.Filename); + TextView fileName = (TextView) view.findViewById(R.id.Filename); String name = file.getFileName(); fileName.setText(name); @@ -126,7 +286,8 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { ImageView localStateView = (ImageView) view.findViewById(R.id.imageView2); localStateView.bringToFront(); - FileDownloaderBinder downloaderBinder = mTransferServiceGetter.getFileDownloaderBinder(); + FileDownloaderBinder downloaderBinder = + mTransferServiceGetter.getFileDownloaderBinder(); FileUploaderBinder uploaderBinder = mTransferServiceGetter.getFileUploaderBinder(); if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) { localStateView.setImageResource(R.drawable.downloading_file_indicator); @@ -149,7 +310,9 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { fileSizeV.setVisibility(View.VISIBLE); fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.getFileLength())); lastModV.setVisibility(View.VISIBLE); - lastModV.setText(DisplayUtils.unixTimeToHumanReadable(file.getModificationTimestamp())); + lastModV.setText( + DisplayUtils.unixTimeToHumanReadable(file.getModificationTimestamp()) + ); // this if-else is needed even thoe fav icon is visible by default // because android reuses views in listview if (!file.keepInSync()) { @@ -168,20 +331,42 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { checkBoxV.setImageResource(android.R.drawable.checkbox_off_background); } checkBoxV.setVisibility(View.VISIBLE); + } + + // get Thumbnail if file is image + if (file.isImage()){ + // Thumbnail in Cache? + Bitmap thumbnail = getBitmapFromDiskCache(String.valueOf(file.getRemoteId())); + if (thumbnail != null && !file.needsUpdateThumbnail()){ + fileIcon.setImageBitmap(thumbnail); + } else { + // generate new Thumbnail + if (cancelPotentialWork(file, fileIcon)) { + final ThumbnailGenerationTask task = + new ThumbnailGenerationTask(fileIcon); + final AsyncDrawable asyncDrawable = + new AsyncDrawable(mContext.getResources(), defaultImg, task); + fileIcon.setImageDrawable(asyncDrawable); + task.execute(file); + } + } + } else { + fileIcon.setImageResource( + DisplayUtils.getResourceId(file.getMimetype(), file.getFileName()) + ); } - - fileIcon.setImageResource(DisplayUtils.getResourceId(file.getMimetype(), file.getFileName())); - + if (checkIfFileIsSharedWithMe(file)) { sharedWithMeIconV.setVisibility(View.VISIBLE); } } else { - fileSizeV.setVisibility(View.INVISIBLE); //fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.getFileLength())); lastModV.setVisibility(View.VISIBLE); - lastModV.setText(DisplayUtils.unixTimeToHumanReadable(file.getModificationTimestamp())); + lastModV.setText( + DisplayUtils.unixTimeToHumanReadable(file.getModificationTimestamp()) + ); checkBoxV.setVisibility(View.GONE); view.findViewById(R.id.imageView3).setVisibility(View.GONE); @@ -189,7 +374,9 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { fileIcon.setImageResource(R.drawable.shared_with_me_folder); sharedWithMeIconV.setVisibility(View.VISIBLE); } else { - fileIcon.setImageResource(DisplayUtils.getResourceId(file.getMimetype(), file.getFileName())); + fileIcon.setImageResource( + DisplayUtils.getResourceId(file.getMimetype(), file.getFileName()) + ); } // If folder is sharedByLink, icon folder must be changed to @@ -208,6 +395,35 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { return view; } + + public static boolean cancelPotentialWork(OCFile file, ImageView imageView) { + final ThumbnailGenerationTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + if (bitmapWorkerTask != null) { + final OCFile bitmapData = bitmapWorkerTask.file; + // If bitmapData is not yet set or it differs from the new data + if (bitmapData == null || bitmapData != file) { + // Cancel previous task + bitmapWorkerTask.cancel(true); + } else { + // The same work is already in progress + return false; + } + } + // No task associated with the ImageView, or an existing task was cancelled + return true; + } + + private static ThumbnailGenerationTask getBitmapWorkerTask(ImageView imageView) { + if (imageView != null) { + final Drawable drawable = imageView.getDrawable(); + if (drawable instanceof AsyncDrawable) { + final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; + return asyncDrawable.getBitmapWorkerTask(); + } + } + return null; + } @Override public int getViewTypeCount() { @@ -226,8 +442,10 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { /** * Change the adapted directory for a new one - * @param directory New file to adapt. Can be NULL, meaning "no content to adapt". - * @param updatedStorageManager Optional updated storage manager; used to replace mStorageManager if is different (and not NULL) + * @param directory New file to adapt. Can be NULL, meaning + * "no content to adapt". + * @param updatedStorageManager Optional updated storage manager; used to replace + * mStorageManager if is different (and not NULL) */ public void swapDirectory(OCFile directory, FileDataStorageManager updatedStorageManager) { mFile = directory; @@ -273,7 +491,9 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { * @return boolean: True if it is shared with me and false if it is not */ private boolean checkIfFileIsSharedWithMe(OCFile file) { - return (mFile.getPermissions() != null && !mFile.getPermissions().contains(PERMISSION_SHARED_WITH_ME) - && file.getPermissions() != null && file.getPermissions().contains(PERMISSION_SHARED_WITH_ME)); + return (mFile.getPermissions() != null + && !mFile.getPermissions().contains(PERMISSION_SHARED_WITH_ME) + && file.getPermissions() != null + && file.getPermissions().contains(PERMISSION_SHARED_WITH_ME)); } } diff --git a/src/com/owncloud/android/utils/BitmapUtils.java b/src/com/owncloud/android/utils/BitmapUtils.java new file mode 100644 index 00000000..687b5a4f --- /dev/null +++ b/src/com/owncloud/android/utils/BitmapUtils.java @@ -0,0 +1,99 @@ +/* ownCloud Android client application + * Copyright (C) 2012-2014 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +package com.owncloud.android.utils; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BitmapFactory.Options; + +/** + * Utility class with methods for decoding Bitmaps. + * + * @author David A. Velasco + */ +public class BitmapUtils { + + + /** + * Decodes a bitmap from a file containing it minimizing the memory use, known that the bitmap + * will be drawn in a surface of reqWidth x reqHeight + * + * @param srcPath Absolute path to the file containing the image. + * @param reqWidth Width of the surface where the Bitmap will be drawn on, in pixels. + * @param reqHeight Height of the surface where the Bitmap will be drawn on, in pixels. + * @return + */ + public static Bitmap decodeSampledBitmapFromFile(String srcPath, int reqWidth, int reqHeight) { + + // set desired options that will affect the size of the bitmap + final Options options = new Options(); + options.inScaled = true; + options.inPurgeable = true; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { + options.inPreferQualityOverSpeed = false; + } + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { + options.inMutable = false; + } + + // make a false load of the bitmap to get its dimensions + options.inJustDecodeBounds = true; + + BitmapFactory.decodeFile(srcPath, options); + + // calculate factor to subsample the bitmap + options.inSampleSize = calculateSampleFactor(options, reqWidth, reqHeight); + + // decode bitmap with inSampleSize set + options.inJustDecodeBounds = false; + return BitmapFactory.decodeFile(srcPath, options); + + } + + + /** + * Calculates a proper value for options.inSampleSize in order to decode a Bitmap minimizing + * the memory overload and covering a target surface of reqWidth x reqHeight if the original + * image is big enough. + * + * @param options Bitmap decoding options; options.outHeight and options.inHeight should + * be set. + * @param reqWidth Width of the surface where the Bitmap will be drawn on, in pixels. + * @param reqHeight Height of the surface where the Bitmap will be drawn on, in pixels. + * @return The largest inSampleSize value that is a power of 2 and keeps both + * height and width larger than reqWidth and reqHeight. + */ + private static int calculateSampleFactor(Options options, int reqWidth, int reqHeight) { + + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + + if (height > reqHeight || width > reqWidth) { + final int halfHeight = height / 2; + final int halfWidth = width / 2; + + while ((halfHeight / inSampleSize) > reqHeight + && (halfWidth / inSampleSize) > reqWidth) { + inSampleSize *= 2; + } + } + + return inSampleSize; + } + +}