From: David A. Velasco Date: Mon, 15 Sep 2014 07:41:25 +0000 (+0200) Subject: Merge branch 'develop' of https://github.com/tobiasKaminsky/android into thumbnails_f... X-Git-Tag: oc-android-1.7.0_signed~163^2~8 X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/commitdiff_plain/aae90eb1aa42a11449e30330e3e23e1e9e5eebb6?hp=041bc268424d951a2c5546ccc70f4bdbd65ddf59 Merge branch 'develop' of https://github.com/tobiasKaminsky/android into 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/ui/adapter/DiskLruImageCache.java b/src/com/owncloud/android/ui/adapter/DiskLruImageCache.java new file mode 100644 index 00000000..7d122491 --- /dev/null +++ b/src/com/owncloud/android/ui/adapter/DiskLruImageCache.java @@ -0,0 +1,170 @@ +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 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 String TAG = "DiskLruImageCache"; + + public DiskLruImageCache( Context context,String uniqueName, int diskCacheSize, + CompressFormat compressFormat, int quality ) { + try { + final File diskCacheDir = getDiskCacheDir(context, uniqueName ); + mDiskCache = DiskLruCache.open( + diskCacheDir, CACHE_VERSION, VALUE_COUNT, diskCacheSize + ); + mCompressFormat = compressFormat; + mCompressQuality = quality; + } catch (IOException e) { + e.printStackTrace(); + } + } + + 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("DiskCache", "create dir: " + cachePath + File.separator + uniqueName); + + return new File(cachePath + File.separator + uniqueName); + } + + public void put( String key, Bitmap data ) { + + DiskLruCache.Editor editor = null; + try { + editor = mDiskCache.edit( key ); + 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 " + key ); + } + } else { + editor.abort(); + if ( BuildConfig.DEBUG ) { + Log.d( "cache_test_DISK_", "ERROR on: image put on disk cache " + key ); + } + } + } catch (IOException e) { + if ( BuildConfig.DEBUG ) { + Log.d( "cache_test_DISK_", "ERROR on: image put on disk cache " + key ); + } + try { + if ( editor != null ) { + editor.abort(); + } + } catch (IOException ignored) { + } + } + + } + + public Bitmap getBitmap( String key ) { + + Bitmap bitmap = null; + DiskLruCache.Snapshot snapshot = null; + try { + + snapshot = mDiskCache.get( key ); + 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 " + key); + } + + return bitmap; + + } + + public boolean containsKey( String key ) { + + boolean contained = false; + DiskLruCache.Snapshot snapshot = null; + try { + snapshot = mDiskCache.get( key ); + 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(); + } + +} \ 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..172341c4 100644 --- a/src/com/owncloud/android/ui/adapter/FileListListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FileListListAdapter.java @@ -17,10 +17,25 @@ */ package com.owncloud.android.ui.adapter; +import java.io.File; +import java.io.IOException; +import java.lang.ref.WeakReference; +//import java.net.URLEncoder; import java.util.Vector; import android.accounts.Account; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; 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; @@ -36,15 +51,30 @@ import com.owncloud.android.datamodel.FileDataStorageManager; 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.lib.common.OwnCloudAccount; +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; +import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException; import com.owncloud.android.ui.activity.ComponentsGetter; import com.owncloud.android.utils.DisplayUtils; +/* +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.util.EntityUtils; +*/ + /** * This Adapter populates a ListView with all files and folders in an ownCloud * instance. * * @author Bartek Przybylski + * @Author Tobias Kaminsky * */ public class FileListListAdapter extends BaseAdapter implements ListAdapter { @@ -59,15 +89,184 @@ 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 OwnCloudClient mClient; + 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) { + mThumbnailCache = new DiskLruImageCache(mContext, "thumbnailCache", + DISK_CACHE_SIZE, mCompressFormat, mCompressQuality); + + try { + OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, mContext); + mClient = OwnCloudClientManagerFactory.getDefaultSingleton(). + getClientFor(ocAccount, mContext); + } catch (AccountNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (AuthenticatorException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (OperationCanceledException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + 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) { + file = params[0]; + final String imageKey = String.valueOf(file.getRemoteId()); + + // Check disk cache in background thread + Bitmap thumbnail = getBitmapFromDiskCache(imageKey); + + // Not found in disk cache + if (thumbnail == null) { + // 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 = BitmapFactory.decodeFile(file.getStoragePath()); + thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px); + + // Add thumbnail to cache + addBitmapToCache(imageKey, thumbnail); + + } else { + // Download thumbnail from server + // Commented out as maybe changes to client library are needed +// DefaultHttpClient httpclient = new DefaultHttpClient(); +// try { +// httpclient.getCredentialsProvider().setCredentials( +// new AuthScope(mClient.getBaseUri().toString().replace("https://", ""), 443), +// new UsernamePasswordCredentials(mClient.getCredentials().getUsername(), mClient.getCredentials().getAuthToken())); +// +// +// HttpGet httpget = new HttpGet(mClient.getBaseUri() + "/ocs/v1.php/thumbnail?x=50&y=50&path=" + URLEncoder.encode(file.getRemotePath(), "UTF-8")); +// HttpResponse response = httpclient.execute(httpget); +// HttpEntity entity = response.getEntity(); +// +// if (entity != null) { +// byte[] bytes = EntityUtils.toByteArray(entity); +// Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); +// thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px); +// +// // Add thumbnail to cache +// if (thumbnail != null){ +// addBitmapToCache(imageKey, thumbnail); +// } +// } +// } catch(Exception e){ +// e.printStackTrace(); +// }finally { +// httpclient.getConnectionManager().shutdown(); +// } + } + } + 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.getBitmap(key) == 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 +311,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 +325,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 +349,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 +370,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){ + 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 +413,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 +434,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 +481,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 +530,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)); } }