package com.amirarcane.recentimages.thumbnailOptions; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapFactory.Options; import android.graphics.Matrix; import android.media.ExifInterface; import android.net.Uri; import android.os.Handler; import android.provider.MediaStore; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.CursorAdapter; import android.widget.ImageView; import com.amirarcane.recentimages.R; import com.jess.ui.TwoWayAbsListView; import java.io.IOException; import java.lang.ref.SoftReference; import java.util.LinkedList; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class ImageAdapter extends CursorAdapter { private static final String[] IMAGE_PROJECTION = { MediaStore.Images.ImageColumns._ID, MediaStore.Images.ImageColumns.DISPLAY_NAME, }; public static final int IMAGE_ID_COLUMN = 0; public static final int IMAGE_NAME_COLUMN = 1; public static final boolean DEBUG = false; private static final String TAG = "ImageAdapter"; public static float IMAGE_WIDTH = 70; public static float IMAGE_HEIGHT = 70; public static float IMAGE_PADDING = 0; private static final Map<Long, SoftReference<ReplaceableBitmapDrawable>> sImageCache = new ConcurrentHashMap<Long, SoftReference<ReplaceableBitmapDrawable>>(); private static Options sBitmapOptions = new Options(); private final Context mContext; private Bitmap mDefaultBitmap; private final ContentResolver mContentResolver; private final Handler mHandler; private float mScale; private int mImageWidth; private int mImageHeight; private int mImagePadding; public ImageView.ScaleType SCALE_TYPE = ImageView.ScaleType.CENTER_CROP; public static int IN_SAMPLE_SIZE = 3; public static int KIND = 1; public static int DRAWABLE = R.drawable.spinner_black_76; public ImageAdapter(Context context, Cursor c) { this(context, c, true); } public ImageAdapter(Context context, Cursor c, boolean autoRequery) { super(context, c, autoRequery); mContext = context; init(c); mContentResolver = context.getContentResolver(); mHandler = new Handler(); } private void init(Cursor c) { mDefaultBitmap = BitmapFactory.decodeResource(mContext.getResources(), DRAWABLE); mScale = mContext.getResources().getDisplayMetrics().density; mImageWidth = (int) (IMAGE_WIDTH * mScale); mImageHeight = (int) (IMAGE_HEIGHT * mScale); mImagePadding = (int) (IMAGE_PADDING * mScale); sBitmapOptions.inSampleSize = IN_SAMPLE_SIZE; } @Override public int getItemViewType(int position) { return 0; } @Override public int getViewTypeCount() { return 1; } @Override public void bindView(View view, Context context, Cursor cursor) { int id = cursor.getInt(IMAGE_ID_COLUMN); ((ImageView) view).setImageDrawable(getCachedThumbnailAsync( ContentUris.withAppendedId(MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI, id))); } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { ImageView imageView = new BetterImageView(mContext.getApplicationContext()); imageView.setLayoutParams(new TwoWayAbsListView.LayoutParams(mImageWidth, mImageHeight)); imageView.setPadding(mImagePadding, mImagePadding, mImagePadding, mImagePadding); imageView.setScaleType(SCALE_TYPE); return imageView; } public void cleanup() { cleanupCache(); } private static Bitmap loadThumbnail(ContentResolver cr, Uri uri) { int id = (int) ContentUris.parseId(uri); int orientation = getOrientation(cr, id); Matrix matrix = new Matrix(); matrix.postRotate(orientation); Log.d("Orientation", String.valueOf(orientation)); Bitmap bitmap = MediaStore.Images.Thumbnails.getThumbnail( cr, id, KIND, sBitmapOptions); bitmap = crop(bitmap, matrix); return bitmap; } private static int getOrientation(ContentResolver cr, int id) { String photoID = String.valueOf(id); Cursor cursor = cr.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[] {MediaStore.Images.Media.ORIENTATION}, MediaStore.Images.Media._ID + "=?", new String[] {"" + photoID}, null); int orientation = -1; if (cursor.getCount() != 1) { return -1; } if (cursor.moveToFirst()) { orientation = cursor.getInt(0); } cursor.close(); return orientation; } private static Bitmap crop(Bitmap srcBmp, Matrix matrix) { Bitmap dstBmp; if (srcBmp.getWidth() >= srcBmp.getHeight()) { dstBmp = Bitmap.createBitmap( srcBmp, srcBmp.getWidth() / 2 - srcBmp.getHeight() / 2, 0, srcBmp.getHeight(), srcBmp.getHeight(), matrix, true ); } else { dstBmp = Bitmap.createBitmap( srcBmp, 0, srcBmp.getHeight() / 2 - srcBmp.getWidth() / 2, srcBmp.getWidth(), srcBmp.getWidth(), matrix, true ); } return dstBmp; } /** * Retrieves a drawable from the image cache, identified by the specified id. * If the drawable does not exist in the cache, it is loaded asynchronously and added to the cache. * If the drawable cannot be added to the cache, the specified default drawable is * returned. * * @param uri The uri of the drawable to retrieve * @return The drawable identified by id or defaultImage */ private ReplaceableBitmapDrawable getCachedThumbnailAsync(Uri uri) { ReplaceableBitmapDrawable drawable = null; long id = ContentUris.parseId(uri); WorkQueue wq = WorkQueue.getInstance(); synchronized (wq.mQueue) { SoftReference<ReplaceableBitmapDrawable> reference = sImageCache.get(id); if (reference != null) { drawable = reference.get(); } if (drawable == null || !drawable.isLoaded()) { drawable = new ReplaceableBitmapDrawable(mDefaultBitmap); sImageCache.put(id, new SoftReference<ReplaceableBitmapDrawable>(drawable)); ImageLoadingArgs args = new ImageLoadingArgs(mContentResolver, mHandler, drawable, uri); wq.execute(new ImageLoader(args)); } } return drawable; } /** * Removes all the callbacks from the drawables stored in the memory cache. This * method must be called from the onDestroy() method of any activity using the * cached drawables. Failure to do so will result in the entire activity being * leaked. */ public static void cleanupCache() { for (SoftReference<ReplaceableBitmapDrawable> reference : sImageCache.values()) { final ReplaceableBitmapDrawable drawable = reference.get(); if (drawable != null) drawable.setCallback(null); } } /** * Deletes the specified drawable from the cache. * * @param uri The uri of the drawable to delete from the cache */ public static void deleteCachedCover(Uri uri) { sImageCache.remove(ContentUris.parseId(uri)); } /** * Class to asynchronously perform the loading of the bitmap */ public static class ImageLoader implements Runnable { protected ImageLoadingArgs mArgs = null; public ImageLoader(ImageLoadingArgs args) { mArgs = args; } public void run() { final Bitmap bitmap = loadThumbnail(mArgs.mContentResolver, mArgs.mUri); if (DEBUG) Log.i(TAG, "run() bitmap: " + bitmap); if (bitmap != null) { final ReplaceableBitmapDrawable d = mArgs.mDrawable; if (d != null) { mArgs.mHandler.post(new Runnable() { public void run() { if (DEBUG) Log.i(TAG, "ImageLoader.run() - setting the bitmap for uri: " + mArgs.mUri); d.setBitmap(bitmap); } }); } else { Log.e(TAG, "ImageLoader.run() - FastBitmapDrawable is null for uri: " + mArgs.mUri); } } else { Log.e(TAG, "ImageLoader.run() - bitmap is null for uri: " + mArgs.mUri); } } public void cancel() { sImageCache.remove(mArgs.mUri); } @Override public boolean equals(Object obj) { if (obj != null && obj instanceof ImageLoader && mArgs.mUri != null) { return mArgs.mUri.equals(((ImageLoader) obj).mArgs); } return false; } @Override public int hashCode() { return mArgs.mUri.hashCode(); } } /** * Class to hold all the parts necessary to load an image */ public static class ImageLoadingArgs { ContentResolver mContentResolver; Handler mHandler; ReplaceableBitmapDrawable mDrawable; Uri mUri; /** * @param contentResolver - ContentResolver to use * @param drawable - FastBitmapDrawable whose underlying bitmap should be replaced with new bitmap * @param uri - Uri of image location */ public ImageLoadingArgs(ContentResolver contentResolver, Handler handler, ReplaceableBitmapDrawable drawable, Uri uri) { mContentResolver = contentResolver; mHandler = handler; mDrawable = drawable; mUri = uri; } } public static class WorkQueue { private static WorkQueue sInstance = null; private static final int NUM_OF_THREADS = 1; private static final int MAX_QUEUE_SIZE = 21; private final int mNumOfThreads; private final PoolWorker[] mThreads; protected final LinkedList<ImageLoader> mQueue; public static synchronized WorkQueue getInstance() { if (sInstance == null) { sInstance = new WorkQueue(NUM_OF_THREADS); } return sInstance; } private WorkQueue(int nThreads) { mNumOfThreads = nThreads; mQueue = new LinkedList<ImageLoader>(); mThreads = new PoolWorker[mNumOfThreads]; for (int i = 0; i < mNumOfThreads; i++) { mThreads[i] = new PoolWorker(); mThreads[i].start(); } } public void execute(ImageLoader r) { synchronized (mQueue) { mQueue.remove(r); if (mQueue.size() > MAX_QUEUE_SIZE) { mQueue.removeFirst().cancel(); } mQueue.addLast(r); mQueue.notify(); } } private class PoolWorker extends Thread { private boolean mRunning = true; @Override public void run() { Runnable r; while (mRunning) { synchronized (mQueue) { while (mQueue.isEmpty() && mRunning) { try { mQueue.wait(); } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); } } r = mQueue.removeFirst(); } // If we don't catch RuntimeException, // the pool could leak threads try { r.run(); } catch (RuntimeException e) { Log.e(TAG, "RuntimeException", e); } } Log.i(TAG, "PoolWorker finished"); } public void stopWorker() { mRunning = false; } } } }