/**
* This software is intended for educational purposes and there is no
* warranty, so use at your own risk.
*/
package vandy.mooc.utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import vandy.mooc.R;
import vandy.mooc.common.Utils;
import android.app.ActivityManager;
import android.app.ActivityManager.MemoryInfo;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.net.Uri;
import android.os.Environment;
import android.util.Base64;
import android.util.Log;
import android.widget.ImageView;
/**
* This helper class encapsulates several static methods that are used
* to download image files.
*/
public class NetUtils {
/**
* Used for debugging.
*/
private final static String TAG =
NetUtils.class.getSimpleName();
/**
* Size of each file I/O operation.
*/
private static final int BUFLEN = 1024;
/**
* Display a @a bitmapImage on an @a imageView.
*/
public static void displayImage(Context context,
Bitmap bitmapImage,
ImageView imageView) {
if (bitmapImage != null || imageView != null)
imageView.setImageBitmap(bitmapImage);
else
Utils.showToast(context,
"image or ImageView is corrupted");
}
/**
* Decode an image located at @a pathToImageFile and return a
* Bitmap to the image. This method scales the image to avoid
* out-of-memory exceptions when decoding large images.
*/
public static Bitmap decodeImageFromPath(Context context,
Uri pathToImageFile) {
ActivityManager mgr = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
MemoryInfo info = new ActivityManager.MemoryInfo();
mgr.getMemoryInfo(info);
BitmapFactory.Options options =
new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.inPreferredConfig = Config.ARGB_8888;
BitmapFactory.decodeFile(pathToImageFile.toString(),
options);
int ratio =
(int) (4 * (long) options.outHeight
* (long) options.outWidth * (long) 4
/ (info.availMem + 1));
options.inSampleSize = ratio;
options.inJustDecodeBounds = false;
try (InputStream inputStream =
new FileInputStream(pathToImageFile.toString())) {
return BitmapFactory.decodeFile(pathToImageFile.toString(),
options);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Apply a grayscale filter to the @a imageEntity and return it.
*/
public static Uri grayScaleFilter(Context context,
Uri pathToImageFile,
Uri directoryPathname) {
Bitmap originalImage =
decodeImageFromPath(context,
pathToImageFile);
// Bail out if something is wrong with the image.
if (originalImage == null)
return null;
Bitmap grayScaleImage =
originalImage.copy(originalImage.getConfig(),
true);
boolean hasTransparent = grayScaleImage.hasAlpha();
int width = grayScaleImage.getWidth();
int height = grayScaleImage.getHeight();
// A common pixel-by-pixel grayscale conversion algorithm
// using values obtained from en.wikipedia.org/wiki/Grayscale.
for (int i = 0; i < height; ++i) {
// Break out if we've been interrupted.
if (Thread.interrupted())
return null;
for (int j = 0; j < width; ++j) {
// Check if the pixel is transparent in the original
// by checking if the alpha is 0.
if (hasTransparent
&& ((grayScaleImage.getPixel(j, i)
& 0xff000000) >> 24) == 0)
continue;
// Convert the pixel to grayscale.
int pixel = grayScaleImage.getPixel(j, i);
int grayScale =
(int) (Color.red(pixel) * .299
+ Color.green(pixel) * .587
+ Color.blue(pixel) * .114);
grayScaleImage.setPixel(j, i,
Color.rgb(grayScale,
grayScale,
grayScale));
}
}
// Create a filePath to a temporary file.
File filePath =
new File(openDirectory(directoryPathname),
getUniqueFilename
(Uri.parse(pathToImageFile.getLastPathSegment())));
try (FileOutputStream fileOutputStream =
new FileOutputStream(filePath)) {
grayScaleImage.compress(CompressFormat.JPEG, 100,
fileOutputStream);
// Create a URI from the file.
Uri uri = Uri.fromFile(filePath);
return NetUtils.createDirectoryAndSaveFile
(context,
new URL(uri.toString()),
Uri.parse(uri.getLastPathSegment()),
directoryPathname);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Download the image located at the provided Internet url using
* the URL class, store it on the android file system using a
* FileOutputStream, and return the path to the image file on
* disk.
*
* @param context
* The context in which to write the file.
* @param url
* The URL of the image to download.
* @param directoryPathname
* Pathname of the directory to write the file.
*
* @return
* Absolute path to the downloaded image file on the file
* system.
*/
public static Uri downloadImage(Context context,
Uri url,
Uri directoryPathname) {
try {
if (!isExternalStorageWritable()) {
Log.d(TAG,
"external storage is not writable");
return null;
}
// Create an output file and save the image referenced
// at the URL into it.
return NetUtils.createDirectoryAndSaveFile
(context,
new URL(url.toString()),
Uri.parse(url.getLastPathSegment()),
directoryPathname);
} catch (Exception e) {
Log.e(TAG,
"Exception while downloading -- returning null."
+ e.toString());
return null;
}
}
/**
* Returns a open File if @a directoryPath points to a valid
* directory, else null.
*/
private static File openDirectory(Uri directoryPathname) {
File d = new File(directoryPathname.toString());
if (!d.exists() && !d.mkdir())
return null;
else
return d;
}
/**
* Decode an InputStream into a Bitmap and store it in a file on
* the device.
*
* @param context
* The context in which to write the file.
* @param url
* URL to the resource (e.g., local or remote file).
* @param fileName
* Name of the file.
* @param directoryPathname
* Pathname of the directory to write the file.
*
* @return
* Absolute path to the downloaded image file on the file
* system.
*/
private static Uri createDirectoryAndSaveFile(Context context,
URL url,
Uri fileName,
Uri directoryPathname) {
try {
// Bail out of we get an invalid bitmap.
if (url == null)
return null;
// Bail if the fileName is null as well.
if (fileName == null) {
return null;
}
// Create a directory path.
File directoryPath =
new File(directoryPathname.toString());
// If the directory doesn't exist already then create it.
if (!directoryPath.exists())
directoryPath.mkdirs();
// Create a filePath within the directoryPath.
File filePath =
new File(directoryPath,
NetUtils.getUniqueFilename(fileName));
// Delete the file if it already exists.
if (filePath.exists())
filePath.delete();
// Pre-validate file.
try (InputStream is = (InputStream) url.getContent()) {
BitmapFactory.Options options =
new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, options);
if (options.outMimeType == null)
return null;
} catch (Exception e) {
return null; // Indicate a failure.
}
// Get the content of the resource at the url and save it
// to an output file.
try (InputStream is = (InputStream) url.getContent();
OutputStream os = new FileOutputStream(filePath)) {
copyFile(is, os);
} catch (Exception e) {
return null; // Indicate a failure.
}
// Get the absolute path of the image.
String absolutePathToImage = filePath.getAbsolutePath();
Log.d(TAG,
"absolute path to image file is "
+ absolutePathToImage);
// Return the absolute path to the image file.
return Uri.parse(absolutePathToImage);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* This method checks if we can write image to external storage.
*
* @return true if an image can be written, and false otherwise
*/
private static boolean isExternalStorageWritable() {
return Environment.MEDIA_MOUNTED.equals
(Environment.getExternalStorageState());
}
/**
* Create a filename that contains a timestamp which makes it
* unique.
*
* @param filename
* The name of a file that we'd like to make unique.
* @return
* String containing the unique temporary filename.
*/
static private String getUniqueFilename(final Uri filename) {
return Base64.encodeToString((filename.toString()
+ System.currentTimeMillis()
+ Thread.currentThread().getName()).getBytes(),
Base64.NO_WRAP);
// Use this implementation if you don't want to keep filling
// up your file system with temp files..
//
// return Base64.encodeToString(filename.getBytes(), Base64.NO_WRAP);
}
/**
* Copy the contents of the @a inputStream to the @a outputStream.
* @throws IOException
*/
private static void copyFile(InputStream inputStream,
OutputStream outputStream)
throws IOException {
byte[] buffer = new byte[BUFLEN];
for (int n; (n = inputStream.read(buffer)) >= 0; )
outputStream.write(buffer, 0, n);
outputStream.flush();
}
/**
* Ensure this class is only used as a utility.
*/
private NetUtils() {
throw new AssertionError();
}
}