package com.pixate.freestyle.util;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.net.Uri;
/**
* Handles {@link Bitmap} retrieval from a remote location. The default behavior
* of this class is very simple. A bitmap will be decoded and returned via a
* {@link BitmapFactory} class. To attach advanced functionality, such as
* caching and authentication, provide your own {@link PXBitmapDownloader} via
* the {@link #setDownloader(PXBitmapDownloader)} method.
*
* @author shalom
*/
public class PXURLBitmapLoader {
public static final long IMAGE_DOWNLOAD_TIMEOUT = 30000;
private static final PXBitmapDownloader DEFAULT_DOWNLOADER = new PXDefaultImageDownloader();
private static PXURLBitmapLoader instance;
private PXBitmapDownloader downloader;
private long imageDownloadTimeout;
/**
* Returns an instance of this bitmap loader.
*
* @return A {@link PXURLBitmapLoader} instance.
*/
public static PXURLBitmapLoader getInstance() {
synchronized (PXURLBitmapLoader.class) {
if (instance == null) {
instance = new PXURLBitmapLoader();
}
}
return instance;
}
// private constructor
private PXURLBitmapLoader() {
this.downloader = DEFAULT_DOWNLOADER;
this.imageDownloadTimeout = IMAGE_DOWNLOAD_TIMEOUT;
}
/**
* Sets the timeout for an image download when the
* {@link #loadBitmap(Uri, int, int, LoadingCallback, boolean)} is called
* with a <code>synchronous</code> flag. The default value is
* {@value #IMAGE_DOWNLOAD_TIMEOUT}ms.
*
* @param timeout The timeout in milliseconds
*/
public void setImageDownloadTimeout(long timeout) {
this.imageDownloadTimeout = timeout;
}
/**
* Returns the timeout for an image download when the
* {@link #loadBitmap(Uri, int, int, LoadingCallback, boolean)} is called
* with a <code>synchronous</code> flag.
*
* @return The timeout in milliseconds
*/
public long getImageDownloadTimeout() {
return imageDownloadTimeout;
}
/**
* Loads a {@link Bitmap} from the given {@link Uri}. This call is
* asynchronous, and when the loading is completed the provided callback
* will be informed with a {@link Bitmap} reference.
*
* @param uri
* @param callback a callback implementation that will be informed when the
* bitmap is loaded.
* @param width The bitmap's requested width
* @param height The bitmap's requested height
* @return A {@link Bitmap}
* @see #loadBitmap(Uri, LoadingCallback, boolean)
*/
public static void loadBitmap(Uri uri, int width, int height, LoadingCallback<Bitmap> callback) {
loadBitmap(uri, width, height, callback, false);
}
/**
* Loads a {@link Bitmap} from the given {@link Uri}. This call is can be
* made synchronous and wait until the loading is complete before it
* returns.
*
* @param uri
* @param width The bitmap's requested width
* @param height The bitmap's requested height
* @param callback a callback implementation that will be informed when the
* bitmap is loaded.
* @return A {@link Bitmap}
* @see #loadBitmap(Uri, LoadingCallback)
*/
public static void loadBitmap(Uri uri, int width, int height, LoadingCallback<Bitmap> callback,
boolean synchronous) {
PXURLBitmapLoader loader = getInstance();
loader.doLoad(uri, width, height, callback, synchronous);
}
/**
* Sets a custom {@link PXBitmapDownloader} that will handle the
* {@link Bitmap} downloading (retrieval, caching, etc.).
*
* @param downloader A {@link PXBitmapDownloader}. In case <code>null</code>
* , a default downloader will be used.
*/
public void setDownloader(PXBitmapDownloader downloader) {
if (downloader == null) {
this.downloader = DEFAULT_DOWNLOADER;
} else {
this.downloader = downloader;
}
}
/**
* Do the actual {@link Bitmap} loading.
*
* @param uri
* @param width
* @param height
* @param callback
* @param synchronous
* @throws IOException
*/
private void doLoad(final Uri uri, int width, int height,
final LoadingCallback<Bitmap> callback, boolean synchronous) {
downloader.downloadBitmap(uri, width, height, callback, synchronous);
}
/**
* Default {@link Bitmap} downloader. No caching, or any clever stuff is
* done here.
*/
private static class PXDefaultImageDownloader implements PXBitmapDownloader {
/*
* (non-Javadoc)
* @see
* com.pixate.freestyle.util.PXBitmapDownloader#downloadBitmap(android
* .net.Uri, int, int,
* com.pixate.freestyle.util.PXBitmapDownloaderCallback, boolean)
*/
@Override
public void downloadBitmap(Uri uri, final int width, final int height,
final LoadingCallback<Bitmap> callback, boolean synchronous) {
Thread downloadThread = new Thread(new DownloadRunnable(uri, width, height, callback));
downloadThread.start();
if (synchronous) {
try {
downloadThread.join(PXURLBitmapLoader.getInstance().getImageDownloadTimeout());
} catch (Exception e) {
callback.onError(e);
}
}
}
}
/**
* Calculate the bitmap sample size before we download and store it in
* memory.
*
* @param options
* @param newWidth
* @param newHeight
* @return
*/
public static int calculateSampleSize(BitmapFactory.Options options, int newWidth, int newHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > newHeight || width > newWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) > newHeight && (halfWidth / inSampleSize) > newWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
/**
* A {@link Runnable} that handles the bitmap downloading.
*/
private static class DownloadRunnable implements Runnable {
private Bitmap bitmap;
private Exception error;
private Uri uri;
private int width;
private int height;
private LoadingCallback<Bitmap> callback;
/**
* Constructs a new download runnable.
*
* @param uri
* @param width
* @param height
* @param callback
*/
private DownloadRunnable(Uri uri, int width, int height, LoadingCallback<Bitmap> callback) {
this.uri = uri;
this.width = width;
this.height = height;
this.callback = callback;
}
/*
* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
InputStream is = null;
try {
URL url = new URL(uri.toString());
if (height > 0 && width > 0) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
is = url.openStream();
BitmapFactory.decodeStream(is, new Rect(), options);
try {
is.close();
} catch (IOException e) {
}
// calculates the sample size
options.inSampleSize = calculateSampleSize(options, width, height);
options.inJustDecodeBounds = false;
// grab the bitmap in the requested size
is = url.openStream();
bitmap = BitmapFactory.decodeStream(is, new Rect(), options);
} else {
// no size information, so we just grab the bitmap
// as is.
is = url.openStream();
bitmap = BitmapFactory.decodeStream(is);
}
} catch (Exception e) {
error = e;
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
// Notify the callback
if (bitmap != null) {
callback.onLoaded(bitmap);
} else if (error != null) {
callback.onError(error);
} else {
}
}
}
}