/** * Copyright (C) 2014 android10.org. All rights reserved. * @author Fernando Cejas (the android10 coder) */ package com.fernandocejas.android10.sample.presentation.view.widget; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.util.AttributeSet; import android.util.Log; import android.widget.ImageView; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; /** * Simple implementation of {@link android.widget.ImageView} with extended features like setting an * image from an url and an internal file cache using the application cache directory. */ public class AutoLoadImageView extends ImageView { private static final String BASE_IMAGE_NAME_CACHED = "image_"; private int imagePlaceHolderResourceId = -1; private DiskCache cache = new DiskCache(getContext().getCacheDir()); public AutoLoadImageView(Context context) { super(context); } public AutoLoadImageView(Context context, AttributeSet attrs) { super(context, attrs); } public AutoLoadImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } /** * Set an image from a remote url. * * @param imageUrl The url of the resource to load. */ public void setImageUrl(final String imageUrl) { AutoLoadImageView.this.loadImagePlaceHolder(); if (imageUrl != null) { this.loadImageFromUrl(imageUrl); } else { this.loadImagePlaceHolder(); } } /** * Set a place holder used for loading when an image is being downloaded from the internet. * * @param resourceId The resource id to use as a place holder. */ public void setImagePlaceHolder(int resourceId) { this.imagePlaceHolderResourceId = resourceId; this.loadImagePlaceHolder(); } /** * Invalidate the internal cache by evicting all cached elements. */ public void invalidateImageCache() { if (this.cache != null) { this.cache.evictAll(); } } /** * Loads and image from the internet (and cache it) or from the internal cache. * * @param imageUrl The remote image url to load. */ private void loadImageFromUrl(final String imageUrl) { new Thread() { @Override public void run() { final Bitmap bitmap = AutoLoadImageView.this.getFromCache(getFileNameFromUrl(imageUrl)); if (bitmap != null) { AutoLoadImageView.this.loadBitmap(bitmap); } else { if (isThereInternetConnection()) { final ImageDownloader imageDownloader = new ImageDownloader(); imageDownloader.download(imageUrl, new ImageDownloader.Callback() { @Override public void onImageDownloaded(Bitmap bitmap) { AutoLoadImageView.this.cacheBitmap(bitmap, getFileNameFromUrl(imageUrl)); AutoLoadImageView.this.loadBitmap(bitmap); } @Override public void onError() { AutoLoadImageView.this.loadImagePlaceHolder(); } }); } else { AutoLoadImageView.this.loadImagePlaceHolder(); } } } }.start(); } /** * Run the operation of loading a bitmap on the UI thread. * * @param bitmap The image to load. */ private void loadBitmap(final Bitmap bitmap) { ((Activity) getContext()).runOnUiThread(new Runnable() { @Override public void run() { AutoLoadImageView.this.setImageBitmap(bitmap); } }); } /** * Loads the image place holder if any has been assigned. */ private void loadImagePlaceHolder() { if (this.imagePlaceHolderResourceId != -1) { ((Activity) getContext()).runOnUiThread(new Runnable() { @Override public void run() { AutoLoadImageView.this.setImageResource( AutoLoadImageView.this.imagePlaceHolderResourceId); } }); } } /** * Get a {@link android.graphics.Bitmap} from the internal cache or null if it does not exist. * * @param fileName The name of the file to look for in the cache. * @return A valid cached bitmap, otherwise null. */ private Bitmap getFromCache(String fileName) { Bitmap bitmap = null; if (this.cache != null) { bitmap = this.cache.get(fileName); } return bitmap; } /** * Cache an image using the internal cache. * * @param bitmap The bitmap to cache. * @param fileName The file name used for caching the bitmap. */ private void cacheBitmap(Bitmap bitmap, String fileName) { if (this.cache != null) { this.cache.put(bitmap, fileName); } } /** * Checks if the device has any active internet connection. * * @return true device with internet connection, otherwise false. */ private boolean isThereInternetConnection() { boolean isConnected; ConnectivityManager connectivityManager = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); isConnected = (networkInfo != null && networkInfo.isConnectedOrConnecting()); return isConnected; } /** * Creates a file name from an image url * * @param imageUrl The image url used to build the file name. * @return An String representing a unique file name. */ private String getFileNameFromUrl(String imageUrl) { //we could generate an unique MD5/SHA-1 here String hash = String.valueOf(imageUrl.hashCode()); if (hash.startsWith("-")) { hash = hash.substring(1); } return BASE_IMAGE_NAME_CACHED + hash; } /** * Class used to download images from the internet */ private static class ImageDownloader { interface Callback { void onImageDownloaded(Bitmap bitmap); void onError(); } ImageDownloader() {} /** * Download an image from an url. * * @param imageUrl The url of the image to download. * @param callback A callback used to be reported when the task is finished. */ void download(String imageUrl, Callback callback) { try { URLConnection conn = new URL(imageUrl).openConnection(); conn.connect(); Bitmap bitmap = BitmapFactory.decodeStream(conn.getInputStream()); if (callback != null) { callback.onImageDownloaded(bitmap); } } catch (MalformedURLException e) { reportError(callback); } catch (IOException e) { reportError(callback); } } /** * Report an error to the caller * * @param callback Caller implementing {@link Callback} */ private void reportError(Callback callback) { if (callback != null) { callback.onError(); } } } /** * A simple disk cache implementation */ private static class DiskCache { private static final String TAG = "DiskCache"; private final File cacheDir; DiskCache(File cacheDir) { this.cacheDir = cacheDir; } /** * Get an element from the cache. * * @param fileName The name of the file to look for. * @return A valid element, otherwise false. */ synchronized Bitmap get(String fileName) { Bitmap bitmap = null; File file = buildFileFromFilename(fileName); if (file.exists()) { bitmap = BitmapFactory.decodeFile(file.getPath()); } return bitmap; } /** * Cache an element. * * @param bitmap The bitmap to be put in the cache. * @param fileName A string representing the name of the file to be cached. */ synchronized void put(Bitmap bitmap, String fileName) { File file = buildFileFromFilename(fileName); if (!file.exists()) { try { FileOutputStream fileOutputStream = new FileOutputStream(file); bitmap.compress(Bitmap.CompressFormat.PNG, 90, fileOutputStream); fileOutputStream.flush(); fileOutputStream.close(); } catch (FileNotFoundException e) { Log.e(TAG, e.getMessage()); } catch (IOException e) { Log.e(TAG, e.getMessage()); } } } /** * Invalidate and expire the cache. */ void evictAll() { if (cacheDir.exists()) { for (File file : cacheDir.listFiles()) { file.delete(); } } } /** * Creates a file name from an image url * * @param fileName The image url used to build the file name. * @return A {@link java.io.File} representing a unique element. */ private File buildFileFromFilename(String fileName) { String fullPath = this.cacheDir.getPath() + File.separator + fileName; return new File(fullPath); } } }