package net.frakbot.imageviewex;
import android.content.Context;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v4.util.LruCache;
import android.util.AttributeSet;
import com.foxykeep.datadroid.requestmanager.Request;
import com.foxykeep.datadroid.requestmanager.RequestManager.RequestListener;
import com.jakewharton.DiskLruCache;
import net.frakbot.cache.CacheHelper;
import net.frakbot.imageviewex.listener.ImageViewExRequestListener;
import net.frakbot.imageviewex.requestmanager.ImageViewExRequestFactory;
import net.frakbot.imageviewex.requestmanager.ImageViewExRequestManager;
import java.io.File;
import java.io.IOException;
/**
* Extension of the ImageViewEx that handles the download and caching of
* images and animated GIFs.
*
* @author Francesco Pontillo, Sebastiano Poggi
*/
public class ImageViewNext extends ImageViewEx {
private Drawable loadingD;
private static int classLoadingResId;
private Drawable errorD;
private static int classErrorResId;
private String mUrl;
protected ImageViewExRequestManager mRequestManager;
protected Request mCurrentRequest;
protected RequestListener mCurrentRequestListener;
private static int mMemCacheSize = 10 * 1024 * 1024; // 10MiB
private static LruCache<String, byte[]> mMemCache;
private static int mAppVersion = 1;
private static int mDiskCacheValueCount = 1;
private static int mDiskCacheSize = 50 * 1024 * 1024; // 50MiB
private static DiskLruCache mDiskCache;
private static boolean cacheInit = false;
/** {@inheritDoc} */
public ImageViewNext(Context context) {
super(context);
mRequestManager = ImageViewExRequestManager.from(context);
}
/**
* Creates an instance for the class.
*
* @param context The context to initialize the instance into.
* @param attrs The parameters to initialize the instance with.
*/
public ImageViewNext(Context context, AttributeSet attrs) {
super(context, attrs);
mRequestManager = ImageViewExRequestManager.from(context);
}
/**
* @return The in-memory cache.
*/
public static LruCache<String, byte[]> getMemCache() {
return mMemCache;
}
/**
* @return The disk cache.
*/
public static DiskLruCache getDiskCache() {
return mDiskCache;
}
/**
* @return The in-memory cache size, in bits.
*/
public static int getMemCacheSize() {
return mMemCacheSize;
}
/**
* @param memCacheSize The in-memory cache size to set, in bits.
*/
public static void setMemCacheSize(int memCacheSize) {
ImageViewNext.mMemCacheSize = memCacheSize;
}
/**
* @return The version of the app.
*/
public static int getAppVersion() {
return mAppVersion;
}
/**
* @param appVersion The app version to set.
*/
public static void setAppVersion(int appVersion) {
ImageViewNext.mAppVersion = appVersion;
}
/**
* @return The disk cache max size, in bits.
*/
public static int getDiskCacheSize() {
return mDiskCacheSize;
}
/**
* @param diskCacheSize The disk cache max size to set, in bits.
*/
public static void setDiskCacheSize(int diskCacheSize) {
ImageViewNext.mDiskCacheSize = diskCacheSize;
}
/**
* Initializes both the in-memory and the disk-cache
* at class-level, if it hasn't been done already.
* This method is idempotent.
*/
public static void initCaches(Context context) {
if (!cacheInit) {
mMemCache = new LruCache<String, byte[]>(mMemCacheSize) {
protected int sizeOf(String key, byte[] value) {
return value.length;
}
};
File diskCacheDir =
CacheHelper.getDiskCacheDir(context, "imagecache");
try {
mDiskCache = DiskLruCache.open(
diskCacheDir, mAppVersion, mDiskCacheValueCount, mDiskCacheSize);
} catch (IOException e) { }
cacheInit = true;
}
}
/**
* Sets the loading {@link Drawable} to be used for every {@link ImageViewNext}.
*
* @param classLoadingDrawableResId the {@link int} resource ID of the Drawable
* while loading an image.
*/
public static void setClassLoadingDrawable(int classLoadingDrawableResId) {
classLoadingResId = classLoadingDrawableResId;
}
/**
* Sets the loading {@link Drawable} to be used for this {@link ImageViewNext}.
*
* @param loadingDrawable the {@link Drawable} to display while loading an image.
*/
public void setLoadingDrawable(Drawable loadingDrawable) {
loadingD = loadingDrawable;
}
/**
* Gets the {@link Drawable} to display while loading an image.
*
* @return {@link Drawable} to display while loading.
*/
public Drawable getLoadingDrawable() {
if (loadingD != null) {
return loadingD;
}
else {
return classLoadingResId > 0 ? getResources().getDrawable(classLoadingResId) : null;
}
}
/**
* Sets the error {@link Drawable} to be used for every {@link ImageViewNext}.
*
* @param classErrorDrawableResId the {@link int} resource ID of the Drawable
* to display after an error getting an image.
*/
public static void setClassErrorDrawable(int classErrorDrawableResId) {
classErrorResId = classErrorDrawableResId;
}
/**
* Sets the error {@link Drawable} to be used for this {@link ImageViewNext}.
*
* @param errorDrawable the {@link Drawable} to display after an error getting an image.
*/
public void setErrorDrawable(Drawable errorDrawable) {
errorD = errorDrawable;
}
/**
* Gets the {@link Drawable} to display after an error loading an image.
*
* @return {@link Drawable} to display after an error loading an image.
*/
public Drawable getErrorDrawable() {
return (errorD != null ? errorD : getResources().getDrawable(classErrorResId));
}
/**
* Sets the content of the {@link ImageViewNext} with the data to be downloaded
* from the provided URL.
*
* @param url The URL to download the image from. It can be an animated GIF.
*/
public void setUrl(String url) {
this.mUrl = url;
// Abort the current request before starting another one
if (mCurrentRequest != null
&& mRequestManager.isRequestInProgress(mCurrentRequest)) {
mRequestManager.removeRequestListener(mCurrentRequestListener);
}
// Start the whole retrieval chain
getFromMemCache(url);
}
/**
* Returns the current URL set to the {@link ImageViewNext}.
* The URL will be returned regardless of the existence of
* the image or of the caching/downloading progress.
*
* @return The URL set for this {@link ImageViewNext}.
*/
public String getUrl() {
return this.mUrl;
}
/**
* Tries to get the image from the memory cache.
* @param url The URL to download the image from. It can be an animated GIF.
*/
private void getFromMemCache(String url) {
Request mRequest =
ImageViewExRequestFactory.getImageMemCacheRequest(url);
mCurrentRequestListener = new ImageMemCacheListener(this);
mRequestManager.execute(mRequest, mCurrentRequestListener);
}
/**
* Tries to get the image from the disk cache.
* @param url The URL to download the image from. It can be an animated GIF.
*/
private void getFromDiskCache(String url) {
Request mRequest =
ImageViewExRequestFactory.getImageDiskCacheRequest(url);
mCurrentRequestListener = new ImageDiskCacheListener(this);
mRequestManager.execute(mRequest, mCurrentRequestListener);
}
/**
* Tries to get the image from the network.
* @param url The URL to download the image from. It can be an animated GIF.
*/
private void getFromNetwork(String url) {
Request mRequest =
ImageViewExRequestFactory.getImageDownloaderRequest(url);
mCurrentRequestListener = new ImageDownloadListener(this);
mRequestManager.execute(mRequest, mCurrentRequestListener);
}
/**
* Called when the image is got from whatever the source.
* Override this to get the appropriate callback.
* @param image The image as a byte array.
*/
protected void onSuccess(byte[] image) {
setByteArray(image);
}
/**
* Called when the image is got from the memory cache.
* Override this to get the appropriate callback.
* @param image The image as a byte array.
*/
protected void onMemCacheHit(byte[] image) {
onSuccess(image);
}
/**
* Called when there is a memory cache miss for the image.
* Override this to get the appropriate callback.
*/
protected void onMemCacheMiss() {
Drawable loadingDrawable = getLoadingDrawable();
if (loadingDrawable != null) {
ScaleType scaleType = this.getScaleType();
if (scaleType != null) {
this.setScaleType(scaleType);
} else {
this.setScaleType(ScaleType.CENTER_INSIDE);
}
this.setImageDrawable(loadingDrawable);
if (loadingDrawable instanceof AnimationDrawable) {
((AnimationDrawable) loadingDrawable).start();
}
}
}
/**
* Called when the image is got from the disk cache.
* Override this to get the appropriate callback.
* @param image The image as a byte array.
*/
protected void onDiskCacheHit(byte[] image) {
onSuccess(image);
}
/**
* Called when there is a disk cache miss for the image.
* Override this to get the appropriate callback.
*/
protected void onDiskCacheMiss() {
// No default implementation
}
/**
* Called when the image is got from the network.
* Override this to get the appropriate callback.
* @param image The image as a byte array.
*/
protected void onNetworkHit(byte[] image) {
onSuccess(image);
}
/**
* Called when there is a network miss for the image,
* usually a 404.
* Override this to get the appropriate callback.
*/
protected void onNetworkMiss() {
// No default implementation
}
/**
* Called when the image could not be found anywhere.
* Override this to get the appropriate callback.
*/
protected void onMiss() {
Drawable errorDrawable = getErrorDrawable();
if (getErrorDrawable() != null) {
ScaleType scaleType = this.getScaleType();
if (scaleType != null) {
this.setScaleType(scaleType);
} else {
this.setScaleType(ScaleType.CENTER_INSIDE);
}
this.setImageDrawable(errorDrawable);
if (errorDrawable instanceof AnimationDrawable) {
((AnimationDrawable) errorDrawable).start();
}
}
}
/**
* Sets the image from a byte array.
* @param image The image to set.
*/
private void setByteArray(final byte[] image) {
if (image != null) {
ScaleType scaleType = this.getScaleType();
if (scaleType != null) {
this.setScaleType(scaleType);
}
this.setSource(image);
}
}
/**
* Operation listener for the memory cache retrieval operation.
* @author Francesco Pontillo
*
*/
private class ImageMemCacheListener extends ImageViewExRequestListener {
public ImageMemCacheListener(ImageViewNext imageViewNext) {
super(imageViewNext);
}
@Override
public void onRequestFinished(Request request, Bundle resultData) {
byte[] image =
resultData.getByteArray(ImageViewExRequestFactory.BUNDLE_EXTRA_OBJECT);
if (image == null)
handleMiss();
else
mImageViewNext.onMemCacheHit(image);
}
@Override
public void onRequestConnectionError(Request request, int statusCode) {
handleMiss();
}
@Override
public void onRequestDataError(Request request) {
handleMiss();
}
@Override
public void onRequestCustomError(Request request, Bundle resultData) {
handleMiss();
}
/**
* Generic function to handle the cache miss.
*/
private void handleMiss() {
// Calls the class callback
mImageViewNext.onMemCacheMiss();
// Starts searching in the disk cache
getFromDiskCache(mImageViewNext.getUrl());
}
}
/**
* Operation listener for the disk cache retrieval operation.
* @author Francesco Pontillo
*
*/
private class ImageDiskCacheListener extends ImageViewExRequestListener {
public ImageDiskCacheListener(ImageViewNext imageViewNext) {
super(imageViewNext);
}
@Override
public void onRequestFinished(Request request, Bundle resultData) {
byte[] image =
resultData.getByteArray(ImageViewExRequestFactory.BUNDLE_EXTRA_OBJECT);
if (image == null)
handleMiss();
else
mImageViewNext.onDiskCacheHit(image);
}
@Override
public void onRequestConnectionError(Request request, int statusCode) {
handleMiss();
}
@Override
public void onRequestDataError(Request request) {
handleMiss();
}
@Override
public void onRequestCustomError(Request request, Bundle resultData) {
handleMiss();
}
/**
* Generic function to handle the cache miss.
*/
private void handleMiss() {
// Calls the class callback
mImageViewNext.onDiskCacheMiss();
// Starts searching in the network
getFromNetwork(mImageViewNext.getUrl());
}
}
/**
* Operation listener for the network retrieval operation.
* @author Francesco Pontillo
*
*/
private class ImageDownloadListener extends ImageViewExRequestListener {
public ImageDownloadListener(ImageViewNext imageViewNext) {
super(imageViewNext);
}
@Override
public void onRequestFinished(Request request, Bundle resultData) {
byte[] image =
resultData.getByteArray(ImageViewExRequestFactory.BUNDLE_EXTRA_OBJECT);
if (image == null)
handleMiss();
else
mImageViewNext.onNetworkHit(image);
}
@Override
public void onRequestConnectionError(Request request, int statusCode) {
handleMiss();
}
@Override
public void onRequestDataError(Request request) {
handleMiss();
}
@Override
public void onRequestCustomError(Request request, Bundle resultData) {
handleMiss();
}
/**
* Generic function to handle the network miss.
*/
private void handleMiss() {
// Calls the class callback
mImageViewNext.onNetworkMiss();
// Calss the final miss class callback
// Starts searching in the disk cache
mImageViewNext.onMiss();
}
}
}