package com.sabdroidex.utils; import android.annotation.TargetApi; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; import android.graphics.BitmapFactory.Options; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.TransitionDrawable; import android.os.AsyncTask; import android.os.Debug; import android.support.v4.util.LruCache; import android.util.Log; import android.widget.ImageView; import com.sabdroidex.controllers.sickbeard.SickBeardController; import com.utils.ApacheCredentialProvider; import com.utils.FileUtil; import com.utils.HttpUtil; import java.io.File; import java.io.FileOutputStream; import java.lang.ref.WeakReference; public class ImageWorker { private static final String TAG = ImageWorker.class.getCanonicalName(); private BitmapReader mBitmapReader = null; private Options bgOptions = null; private Bitmap mSickbeardPosterBitmap = null; private Bitmap mSickbeardBannerBitmap = null; private Bitmap mCouchPosterBitmap = null; private Bitmap mCouchBannerBitmap = null; private Resources mResources = null; protected ImageWorker(Context context) { mResources = context.getResources(); bgOptions = new Options(); bgOptions.inPurgeable = true; bgOptions.inPreferredConfig = Config.RGB_565; mBitmapReader = new BitmapReader(0.25f); } /** * Returns true if the current work has been canceled or if there was no * work in progress on this image view. Returns false if the work in * progress deals with the same data. The work is not stopped in that case. */ public static boolean cancelPotentialWork(Object key, ImageView imageView) { final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); if (bitmapWorkerTask != null) { final Object bitmapData = bitmapWorkerTask.key; if (!bitmapData.equals(key)) { bitmapWorkerTask.cancel(true); Log.d(TAG, "cancelPotentialWork - cancelled work for " + key); } else { // The same work is already in progress. return false; } } return true; } /** * @param imageView Any imageView * @return Retrieve the currently active work task (if any) associated with * this imageView. null if there is no such task. */ private static BitmapWorkerTask 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; } public void setSickbeardPosterTemp(int resId) { mSickbeardPosterBitmap = BitmapFactory.decodeResource(mResources, resId, bgOptions); } public void setSickbeardBannerTemp(int resId) { mSickbeardBannerBitmap = BitmapFactory.decodeResource(mResources, resId, bgOptions); } public void setCouchPosterBitmap(int resId) { this.mCouchPosterBitmap = BitmapFactory.decodeResource(mResources, resId, bgOptions); } public void setCouchBannerBitmap(int resId) { this.mCouchBannerBitmap = BitmapFactory.decodeResource(mResources, resId, bgOptions); } /** * Load an image specified by the data parameter into an ImageView. * A memory and disk cache will be used if neither are found, the image will be downloaded onto the device. * * @param data The URL of the image to download. * @param imageView The ImageView to bind the downloaded image to. */ public void loadImage(ImageView imageView, ImageType imageType, String key, Object... data) { if (data == null) { return; } Bitmap bitmap = null; bitmap = mBitmapReader.getBitmapFromMemCache(key); if (bitmap != null) { // Bitmap found in memory cache imageView.setImageBitmap(bitmap); } else if (cancelPotentialWork(key, imageView)) { BitmapWorkerTask task = null; if (imageType == ImageType.SHOW_BANNER) { task = new AsyncShowBanner(imageView, key); } else if (imageType == ImageType.SHOW_POSTER) { task = new AsyncShowPoster(imageView, key); } else if (imageType == ImageType.SHOW_SEASON_POSTER) { task = new AsyncSeasonPoster(imageView, key); } else if (imageType == ImageType.MOVIE_POSTER) { task = new AsyncMoviePoster(imageView, key); } else if (imageType == ImageType.MOVIE_BANNER) { task = new AsyncMovieBanner(imageView, key); } final AsyncDrawable asyncDrawable = new AsyncDrawable(mResources, task); imageView.setImageDrawable(asyncDrawable); task.execute(data); } } public static enum ImageType { SHOW_BANNER, SHOW_POSTER, SHOW_SEASON_POSTER, MOVIE_POSTER, MOVIE_BANNER } public class AsyncDrawable extends BitmapDrawable { private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference; public AsyncDrawable(Resources res, BitmapWorkerTask bitmapWorkerTask) { super(res, bitmapWorkerTask.getLoadingBitmap()); bitmapWorkerTaskReference = new WeakReference<BitmapWorkerTask>(bitmapWorkerTask); } public BitmapWorkerTask getBitmapWorkerTask() { return bitmapWorkerTaskReference.get(); } } public abstract class BitmapWorkerTask extends AsyncTask<Object, Void, Bitmap> { private static final int FADE_IN_TIME = 127; public String key = null; private WeakReference<ImageView> weakViewReference = null; private WeakReference<Bitmap> mLoadingBitmap = null; private boolean mFade = false; protected BitmapWorkerTask(WeakReference<ImageView> weakViewReference, WeakReference<Bitmap> bitmap, String key) { this.weakViewReference = weakViewReference; this.mLoadingBitmap = bitmap; this.key = key; } protected Bitmap getLoadingBitmap() { return mLoadingBitmap.get(); } /** * This background tasks loads the needed bitmap. If the cache is * enabled, it loads it from the external storage, if not or if it does * not exists it will try to download it. */ @Override protected Bitmap doInBackground(Object... params) { Options bgOptions = new Options(); Bitmap bitmap = null; if (Preferences.isEnabled(Preferences.DATA_IMAGE_LOWRES)) { bgOptions.inSampleSize = 2; } else { bgOptions.inSampleSize = 1; } String folderPath = FileUtil.SABDROIDEX + File.separator + params[1] + File.separator; folderPath = folderPath.replace(":", ""); String fileName = getFilename(params); /** * If the cache is enabled we try to read the file from the device * (Default is enabled) */ if (!isCancelled() && bitmap == null && Preferences.isEnabled(Preferences.DATA_IMAGE_CACHE)) { if (Debug.isDebuggerConnected()) { Log.i(getClass().getCanonicalName(), "Loading Bitmap for : " + params[1] + " ... trying to open file."); } FileUtil.createDirectory(folderPath); bitmap = mBitmapReader.getBitmapFromFile(folderPath, fileName, key); } /** * The bitmap object is null if the BitmapFactory has been unable to * decode the Bitmap or if the File does not exists. */ if (!isCancelled() && bitmap == null) { /** * We get the banner from the server */ if (Debug.isDebuggerConnected()) { Log.i(getClass().getCanonicalName(), "Bitmap for : " + params[1] + " not found ... trying to download file."); } String url = getImageURL(params); String savePath = folderPath + fileName; bitmap = mBitmapReader.getBitmapFromWeb(url, savePath, key); } return bitmap; } @Override protected void onPostExecute(Bitmap result) { if (isCancelled()) { result = null; } if (weakViewReference != null && result != null) { final ImageView imageView = getAttachedImageView(); if (imageView != null) { setImage(imageView, result); } } weakViewReference.clear(); } @Override protected void onCancelled(Bitmap result) { result = null; weakViewReference.clear(); super.onCancelled(result); } private void setImage(ImageView imageView, Bitmap bitmap) { if (mFade) { // Create a transition and set the resulting image to be // displayed TransitionDrawable td = new TransitionDrawable(new Drawable[]{new BitmapDrawable(mResources, mLoadingBitmap.get()), new BitmapDrawable(mResources, bitmap)}); imageView.setImageDrawable(td); td.startTransition(FADE_IN_TIME); } else { imageView.setImageBitmap(bitmap); } } /** * Returns the ImageView associated with this task as long as the * ImageView's task still points to this task as well. Returns null * otherwise. */ private ImageView getAttachedImageView() { final ImageView imageView = weakViewReference.get(); final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); if (this == bitmapWorkerTask) { return imageView; } return null; } protected abstract String getImageURL(Object... params); protected abstract String getFilename(Object... params); protected abstract ImageType getImageType(); } public class AsyncShowBanner extends BitmapWorkerTask { public AsyncShowBanner(ImageView imageView, String key) { super(new WeakReference<ImageView>(imageView), new WeakReference<Bitmap>(mSickbeardBannerBitmap), key); } @Override protected String getImageURL(Object... params) { return SickBeardController.getImageURL(SickBeardController.MESSAGE.SHOW_GETBANNER.toString().toLowerCase(), (Integer) params[0]); } @Override protected String getFilename(Object... params) { return "banner.jpg"; } @Override protected ImageType getImageType() { return ImageType.SHOW_BANNER; } } public class AsyncShowPoster extends BitmapWorkerTask { public AsyncShowPoster(ImageView imageView, String key) { super(new WeakReference<ImageView>(imageView), new WeakReference<Bitmap>(mSickbeardPosterBitmap), key); } @Override protected String getImageURL(Object... params) { return SickBeardController.getPosterURL((Integer) params[0]); } @Override protected String getFilename(Object... params) { return "poster.jpg"; } @Override protected ImageType getImageType() { return ImageType.SHOW_POSTER; } } public class AsyncSeasonPoster extends BitmapWorkerTask { public AsyncSeasonPoster(ImageView imageView, String key) { super(new WeakReference<ImageView>(imageView), new WeakReference<Bitmap>(mSickbeardPosterBitmap), key); } @Override protected String getImageURL(Object... params) { return SickBeardController.getSeasonPosterURL((Integer) params[0], (Integer) params[2]); } @Override protected String getFilename(Object... params) { return "season-" + params[2] + ".jpg"; } @Override protected ImageType getImageType() { return ImageType.SHOW_SEASON_POSTER; } } public class AsyncMoviePoster extends BitmapWorkerTask { public AsyncMoviePoster(ImageView imageView, String key) { super(new WeakReference<ImageView>(imageView), new WeakReference<Bitmap>(mCouchPosterBitmap), key); } @Override protected String getImageURL(Object... params) { return (String) params[2]; } @Override protected String getFilename(Object... params) { return "poster.jpg"; } @Override protected ImageType getImageType() { return ImageType.MOVIE_POSTER; } } public class AsyncMovieBanner extends BitmapWorkerTask { public AsyncMovieBanner(ImageView imageView, String key) { super(new WeakReference<ImageView>(imageView), new WeakReference<Bitmap>(mCouchBannerBitmap), key); } @Override protected String getImageURL(Object... params) { return (String) params[2]; } @Override protected String getFilename(Object... params) { return "poster.jpg"; } @Override protected ImageType getImageType() { return ImageType.MOVIE_BANNER; } } public class BitmapReader { private LruCache<String, Bitmap> mMemoryCache; public BitmapReader(float percent) { if (percent < 0.05f || percent > 0.8f) { throw new IllegalArgumentException("setMemCacheSizePercent - percent must be " + "between 0.05 and 0.8 (inclusive)"); } int memCacheSize = Math.round(percent * Runtime.getRuntime().maxMemory() / 1024); mMemoryCache = new LruCache<String, Bitmap>(memCacheSize) { /** * Measure item size in kilobytes rather than units which is * more practical for a bitmap cache */ @Override protected int sizeOf(String key, Bitmap bitmap) { final int bitmapSize = getBitmapSize(bitmap) / 1024; return bitmapSize == 0 ? 1 : bitmapSize; } }; } /** * Get the size in bytes of a bitmap. * * @param bitmap * @return size in bytes */ @TargetApi(12) public int getBitmapSize(Bitmap bitmap) { if (SABDroidConstants.hasHoneycombMR1()) { return bitmap.getByteCount(); } return bitmap.getRowBytes() * bitmap.getHeight(); } /** * Get from memory cache. * * @param data Unique identifier for which item to get * @return The bitmap if found in cache, null otherwise */ public Bitmap getBitmapFromMemCache(String data) { return mMemoryCache.get(data); } /** * This method reads the targeted Bitmap from the local storage. * * @param folderPath The path in where to open the file * @param fileName The name of the file to open in the given folderPath * @param key The key representing the file in the memory cache. * @return The bitmap that is stored in the memory cache. */ public Bitmap getBitmapFromFile(String folderPath, String fileName, String key) { Bitmap bitmap = null; byte[] data; /** * Trying to find Image on Local System */ try { data = FileUtil.getFileAsByteArray(folderPath + File.separator + fileName); bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, bgOptions); if (bitmap != null) { mMemoryCache.put(key, bitmap); } } catch (Throwable e) { Log.e(TAG, " " + e.getLocalizedMessage()); } return mMemoryCache.get(key); } /** * This method downloads the targeted Bitmap from the web and saves it if needed. * * @param url The url from the Bitmap * @param savePath The full path in where to save the file * @param key The key representing the file in the memory cache. * @return The bitmap that is stored in the memory cache. */ public Bitmap getBitmapFromWeb(String url, String savePath, String key) { Bitmap bitmap = null; byte[] data; try { data = HttpUtil.getInstance().getDataAsByteArray(url, ApacheCredentialProvider.getCredentialsProvider()); bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, bgOptions); /** * If the cache is enabled ... (Default is enabled) */ if (Preferences.isEnabled(Preferences.DATA_IMAGE_CACHE)) { /** * ... save it on the device */ FileOutputStream fileOutputStream = new FileOutputStream(savePath); fileOutputStream.write(data); fileOutputStream.flush(); fileOutputStream.close(); } if (bitmap != null) { mMemoryCache.put(key, bitmap); } } catch (Throwable e) { Log.e(TAG, " " + e.getLocalizedMessage()); } return mMemoryCache.get(key); } } }