/*
This file is part of BeepMe.
BeepMe is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
BeepMe is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with BeepMe. If not, see <http://www.gnu.org/licenses/>.
Copyright 2012-2014 Michael Glanznig
http://beepme.yourexp.at
*/
package com.glanznig.beepme.helper;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.glanznig.beepme.BeeperApp;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ThumbnailUtils;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.provider.MediaStore;
import android.util.Log;
import it.sephiroth.android.library.media.ExifInterfaceExtended;
public class PhotoUtils {
private static final String TAG = "PhotoUtils";
private static final String PHOTO_PREFIX = "beeper_img_";
private static final String PHOTO_THUMB_SUFFIX = "_thumb_";
private static final String CHANGE_PHOTO_NAME = "swap";
public static final String NORMAL_MODE_DIR = "normal";
public static final String TEST_MODE_DIR = "testmode";
public static final String THUMB_DIR = "thumbs";
public static final int TAKE_PHOTO_INTENT = 3648;
public static final int CHANGE_PHOTO_INTENT = 3638;
public static final int MSG_PHOTO_LOADED = 48;
public static final int MSG_PHOTO_LOAD_ERROR = 49;
public static final String EXTRA_KEY = MediaStore.EXTRA_OUTPUT;
private static final int PHOTO_QUALITY = 90;
public static Intent getTakePhotoIntent(Context ctx, Date timestamp) {
return getPhotoIntent(ctx, timestamp);
}
public static Intent getChangePhotoIntent(Context ctx) {
return getPhotoIntent(ctx, null);
}
private static Intent getPhotoIntent(Context ctx, Date timestamp) {
BeeperApp app = (BeeperApp)ctx.getApplicationContext();
// external storage is ready and writable - can be used
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
File picDir = null;
// add a sub directory depending on whether we are in test mode
if (!app.getPreferences().isTestMode()) {
picDir = new File(ctx.getExternalFilesDir(Environment.DIRECTORY_PICTURES), NORMAL_MODE_DIR);
}
else {
picDir = new File(ctx.getExternalFilesDir(Environment.DIRECTORY_PICTURES), TEST_MODE_DIR);
}
String picFilename = null;
if (timestamp != null) {
picFilename = PHOTO_PREFIX + new SimpleDateFormat("yyyyMMddHHmmss").format(timestamp) + ".jpg";
}
else {
picFilename = PHOTO_PREFIX + CHANGE_PHOTO_NAME + ".jpg";
}
File pictureFile = new File(picDir, picFilename);
try {
if(pictureFile.exists() == false) {
pictureFile.getParentFile().mkdirs();
pictureFile.createNewFile();
}
} catch (IOException e) {
Log.e(TAG, "unable to create file.", e);
return null;
}
Intent takePic = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
takePic = takePic.putExtra(EXTRA_KEY, Uri.fromFile(pictureFile));
return takePic;
}
return null;
}
public static File[] getPhotos(Context ctx) {
BeeperApp app = (BeeperApp)ctx.getApplicationContext();
// external storage is ready and writable - can be used
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
File picDir = null;
// add a sub directory depending on whether we are in test mode
if (!app.getPreferences().isTestMode()) {
picDir = new File(ctx.getExternalFilesDir(Environment.DIRECTORY_PICTURES), NORMAL_MODE_DIR);
} else {
picDir = new File(ctx.getExternalFilesDir(Environment.DIRECTORY_PICTURES), TEST_MODE_DIR);
}
File[] picFiles = picDir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".jpg");
}
});
return picFiles;
}
return null;
}
public static Bitmap getBitmap(Context ctx, String uri) {
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
try {
return MediaStore.Images.Media.getBitmap(
ctx.getContentResolver(), Uri.fromFile(new File(uri)));
}
catch(IOException ioe) {
Log.e(TAG, ioe.getMessage());
}
}
return null;
}
public static void getAsyncBitmap(Context ctx, final String uri, final Handler handler) {
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
final ContentResolver resolver = ctx.getContentResolver();
Thread bitmapLoader = new Thread() {
public void run() {
try {
Bitmap photo = MediaStore.Images.Media.getBitmap(
resolver, Uri.fromFile(new File(uri)));
Bundle b = new Bundle();
b.putString("uri", uri);
Message msg = handler.obtainMessage(MSG_PHOTO_LOADED, photo);
msg.setData(b);
msg.sendToTarget();
}
catch(IOException ioe) {
Log.e(TAG, ioe.getMessage());
handler.obtainMessage(MSG_PHOTO_LOAD_ERROR).sendToTarget();
}
}
};
bitmapLoader.start();
}
else {
handler.obtainMessage(MSG_PHOTO_LOAD_ERROR).sendToTarget();
}
}
public static boolean swapPhoto(Context ctx, Date timestamp) {
BeeperApp app = (BeeperApp)ctx.getApplicationContext();
// external storage is ready and writable - can be used
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
File picDir = ctx.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
// add a sub directory depending on whether we are in test mode
if (!app.getPreferences().isTestMode()) {
picDir = new File(picDir.getAbsolutePath() + File.separator + "normal");
}
else {
picDir = new File(picDir.getAbsolutePath() + File.separator + "testmode");
}
String picFilename = PHOTO_PREFIX + new SimpleDateFormat("yyyyMMddHHmmss").format(timestamp) + ".jpg";
String swapFilename = PHOTO_PREFIX + CHANGE_PHOTO_NAME + ".jpg";
File pictureFile = new File(picDir, picFilename);
File swapFile = new File(picDir, swapFilename);
if (pictureFile.delete()) {
return swapFile.renameTo(pictureFile);
}
}
return false;
}
public static boolean deletePhoto(Context ctx, String uri) {
return deletePhoto(ctx, uri, false);
}
public static boolean deleteThumbnails(Context ctx, String uri) {
return deletePhoto(ctx, uri, true);
}
private static boolean deletePhoto(Context ctx, String uri, boolean thumbnailsOnly) {
if (uri != null && Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
File photo = new File(uri);
if (photo != null) {
String path = photo.getParent();
// delete thumbnails
BeeperApp app = (BeeperApp)ctx.getApplicationContext();
int[] thumbSizes = app.getPreferences().getThumbnailSizes();
for (int i = 0; i < thumbSizes.length; i++) {
// get name without .jpg extension
String thumbPath = path + File.separator + THUMB_DIR;
String name = photo.getName().substring(0, photo.getName().length() - 4);
name = name + PHOTO_THUMB_SUFFIX + thumbSizes[i] + ".jpg";
File thumb = new File(thumbPath, name);
thumb.delete();
}
if (!thumbnailsOnly) {
photo.delete();
}
}
return true;
}
return false;
}
public static boolean deleteSwapPhoto(Context ctx) {
BeeperApp app = (BeeperApp)ctx.getApplicationContext();
// external storage is ready and writable - can be used
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
File picDir = ctx.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
// add a sub directory depending on whether we are in test mode
if (!app.getPreferences().isTestMode()) {
picDir = new File(picDir.getAbsolutePath() + File.separator + "normal");
}
else {
picDir = new File(picDir.getAbsolutePath() + File.separator + "testmode");
}
String picFilename = PHOTO_PREFIX + CHANGE_PHOTO_NAME + ".jpg";
File pictureFile = new File(picDir, picFilename);
if (pictureFile != null) {
deletePhoto(ctx, pictureFile.getAbsolutePath());
return true;
}
}
return false;
}
public static void regenerateThumbnails(Context ctx, String uri, Handler handler) {
deleteThumbnails(ctx, uri);
final float scale = ctx.getResources().getDisplayMetrics().density;
BeeperApp app = (BeeperApp)ctx.getApplicationContext();
int[] thumbSizes = app.getPreferences().getThumbnailSizes();
for (int i = 0; i < thumbSizes.length; i++) {
generateThumbnail(ctx, uri, thumbSizes[i], (int)(thumbSizes[i] * scale + 0.5f), handler);
}
}
public static void generateThumbnails(Context ctx, String uri, Handler handler) {
final float scale = ctx.getResources().getDisplayMetrics().density;
BeeperApp app = (BeeperApp)ctx.getApplicationContext();
int[] thumbSizes = app.getPreferences().getThumbnailSizes();
for (int i = 0; i < thumbSizes.length; i++) {
generateThumbnail(ctx, uri, thumbSizes[i], (int)(thumbSizes[i] * scale + 0.5f), handler);
}
}
public static void generateThumbnail(Context ctx, String uri, int thumbName, int size, Handler handler) {
if (uri != null && Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
File photo = new File(uri);
if (photo != null) {
String path = photo.getParent() + File.separator + THUMB_DIR;
// get name without .jpg extension
String name = photo.getName().substring(0, photo.getName().length() - 4);
name = name + PHOTO_THUMB_SUFFIX + thumbName + ".jpg";
photo = new File(path, name);
// create thumbs dir if it not already exists
if(!photo.getParentFile().exists()) {
photo.getParentFile().mkdirs();
}
if (photo != null && !photo.exists()) {
AsyncImageScaler scaler = new AsyncImageScaler(ctx, uri, photo.getAbsolutePath(), thumbName, size, size, handler);
scaler.start();
}
}
}
}
public static String getThumbnailUri(String photoUri, int size) {
if (photoUri != null) {
File photo = new File(photoUri);
String path = photo.getParent() + File.separator + THUMB_DIR;
// get name without .jpg extension
String name = photo.getName().substring(0, photo.getName().length() - 4);
name = name + PHOTO_THUMB_SUFFIX + size + ".jpg";
photo = new File(path, name);
return photo.getAbsolutePath();
}
return null;
}
public static boolean isEnabled(Context ctx) {
boolean enabled = true;
//check if device has camera feature
if (!ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
enabled = false;
}
else {
//check if device has app for taking images
List<ResolveInfo> availApps = ctx.getPackageManager().queryIntentActivities(
new Intent(MediaStore.ACTION_IMAGE_CAPTURE), PackageManager.MATCH_DEFAULT_ONLY);
if (availApps.size() == 0) {
enabled = false;
}
//check if image can be saved to external storage
else if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
enabled = false;
}
}
return enabled;
}
public static Bundle getPhotoDimensions(String uri) {
Bundle dim = new Bundle();
if (uri != null) {
try {
BitmapFactory.Options opts = new BitmapFactory.Options();
FileInputStream fileInput = new FileInputStream(uri);
// decode only image size
opts.inJustDecodeBounds = true;
BitmapFactory.decodeStream(fileInput, null, opts);
fileInput.close();
dim.putInt("width", opts.outWidth);
dim.putInt("height", opts.outHeight);
}
catch(Exception e) {}
}
return dim;
}
public static Bitmap scalePhoto(Context ctx, String srcUri, String destUri, int destWidth, int destHeight) {
if (srcUri != null && destWidth > 0 && destHeight > 0) {
try {
BitmapFactory.Options opts = new BitmapFactory.Options();
FileInputStream fileInput = new FileInputStream(srcUri);
// first decode only image size to determine scale factor
opts.inJustDecodeBounds = true;
BitmapFactory.decodeStream(fileInput, null, opts);
fileInput.close();
int srcWidth = opts.outWidth;
int srcHeight = opts.outHeight;
// check if photo needs to be rotated
ExifInterfaceExtended.initialize(ctx);
ExifInterfaceExtended srcExif = new ExifInterfaceExtended(srcUri);
int rotationTag = srcExif.getAttributeInt(ExifInterfaceExtended.TAG_EXIF_ORIENTATION,
ExifInterfaceExtended.ORIENTATION_NORMAL);
int rotateDeg = 0;
if (rotationTag == ExifInterfaceExtended.ORIENTATION_ROTATE_90) {
rotateDeg = 90;
// swap width and height for scaling
int swap = srcWidth;
srcWidth = srcHeight;
srcHeight = swap;
swap = destWidth;
destWidth = destHeight;
destHeight = swap;
}
else if (rotationTag == ExifInterfaceExtended.ORIENTATION_ROTATE_180) {
rotateDeg = 180;
}
else if (rotationTag == ExifInterfaceExtended.ORIENTATION_ROTATE_270) {
rotateDeg = 270;
// swap width and height for scaling
int swap = srcWidth;
srcWidth = srcHeight;
srcHeight = swap;
swap = destWidth;
destWidth = destHeight;
destHeight = swap;
}
float srcRatio = (float)srcWidth / (float)srcHeight;
float destRatio = (float)destWidth / (float)destHeight;
int scale = 1;
while(srcWidth / scale / 2 >= destWidth && srcHeight / scale / 2 >= destHeight) {
scale *= 2;
}
// now decode image with scale factor (inSampleSize)
opts.inJustDecodeBounds = false;
opts.inSampleSize = scale;
fileInput = new FileInputStream(srcUri);
Bitmap photo = BitmapFactory.decodeStream(fileInput, null, opts);
fileInput.close();
// rotate photo (if needed)
Matrix matrix = new Matrix();
Bitmap rotatedPhoto = null;
if (rotateDeg != 0) {
matrix.preRotate(rotateDeg);
rotatedPhoto = Bitmap.createBitmap(photo, 0, 0,
photo.getWidth(), photo.getHeight(), matrix, true);
}
if (rotatedPhoto != null) {
photo.recycle();
photo = rotatedPhoto;
}
// scale photo
Bitmap croppedPhoto = null;
if (Math.abs(srcRatio - destRatio) > 0.001) {
// different ratio: scale to fit CENTER
croppedPhoto = ThumbnailUtils.extractThumbnail(photo, destWidth, destHeight);
if (croppedPhoto != null) {
photo.recycle();
photo = croppedPhoto;
}
}
else {
photo = Bitmap.createScaledBitmap(photo, destWidth, destHeight, true);
}
// save image to file
if (destUri != null) {
FileOutputStream outStream = new FileOutputStream(destUri);
photo.compress(Bitmap.CompressFormat.JPEG, PHOTO_QUALITY, outStream);
outStream.close();
}
// attach exif information from src photo
Bundle exifData = new Bundle();
srcExif.copyTo(exifData);
ExifInterfaceExtended destExif = new ExifInterfaceExtended(destUri);
destExif.copyFrom(exifData, true);
// since we rotated the photo orientation tag should be reset
destExif.setAttribute(ExifInterfaceExtended.TAG_EXIF_ORIENTATION,
String.valueOf(ExifInterfaceExtended.ORIENTATION_NORMAL));
destExif.saveAttributes();
return photo;
}
catch(Exception e) {
Log.e(TAG, "Error while scaling image.", e);
}
}
return null;
}
}