blob: 8cf2bac5405226139797caaab51efe1cae59ed00 [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.cooliris.media;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.ExecutionException;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.location.Location;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.provider.MediaStore.Images;
import android.provider.MediaStore.Images.ImageColumns;
import android.util.Log;
/**
* ImageManager is used to retrieve and store images in the media content
* provider.
*/
public class ImageManager {
private static final String TAG = "ImageManager";
private static final Uri STORAGE_URI = Images.Media.EXTERNAL_CONTENT_URI;
/**
* Enumerate type for the location of the images in gallery.
*/
public static enum DataLocation {
NONE, INTERNAL, EXTERNAL, ALL
}
public static final Bitmap DEFAULT_THUMBNAIL = Bitmap.createBitmap(32, 32, Bitmap.Config.RGB_565);
public static final Bitmap NO_IMAGE_BITMAP = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565);
public static final int SORT_ASCENDING = 1;
public static final int SORT_DESCENDING = 2;
public static final int INCLUDE_IMAGES = (1 << 0);
public static final int INCLUDE_DRM_IMAGES = (1 << 1);
public static final int INCLUDE_VIDEOS = (1 << 2);
public static final String CAMERA_IMAGE_BUCKET_NAME = Environment.getExternalStorageDirectory().toString() + "/DCIM/Camera";
public static final String CAMERA_IMAGE_BUCKET_ID = getBucketId(CAMERA_IMAGE_BUCKET_NAME);
/**
* Matches code in MediaProvider.computeBucketValues. Should be a common
* function.
*/
public static String getBucketId(String path) {
return String.valueOf(path.toLowerCase().hashCode());
}
/**
* OSX requires plugged-in USB storage to have path /DCIM/NNNAAAAA to be
* imported. This is a temporary fix for bug#1655552.
*/
public static void ensureOSXCompatibleFolder() {
File nnnAAAAA = new File(Environment.getExternalStorageDirectory().toString() + "/DCIM/100ANDRO");
if ((!nnnAAAAA.exists()) && (!nnnAAAAA.mkdir())) {
Log.e(TAG, "create NNNAAAAA file: " + nnnAAAAA.getPath() + " failed");
}
}
public static int roundOrientation(int orientationInput) {
int orientation = orientationInput;
if (orientation == -1) {
orientation = 0;
}
orientation = orientation % 360;
int retVal;
if (orientation < (0 * 90) + 45) {
retVal = 0;
} else if (orientation < (1 * 90) + 45) {
retVal = 90;
} else if (orientation < (2 * 90) + 45) {
retVal = 180;
} else if (orientation < (3 * 90) + 45) {
retVal = 270;
} else {
retVal = 0;
}
return retVal;
}
/**
* @return true if the mimetype is an image mimetype.
*/
public static boolean isImageMimeType(String mimeType) {
return mimeType.startsWith("image/");
}
/**
* @return true if the mimetype is a video mimetype.
*/
public static boolean isVideoMimeType(String mimeType) {
return mimeType.startsWith("video/");
}
public static void setImageSize(ContentResolver cr, Uri uri, long size) {
ContentValues values = new ContentValues();
values.put(Images.Media.SIZE, size);
cr.update(uri, values, null, null);
}
/**
* Stores a bitmap or a jpeg byte array to a file (using the specified
* directory and filename). Also add an entry to the media store for
* this picture. The title, dateTaken, location are attributes for the
* picture. The degree is a one element array which returns the orientation
* of the picture.
*/
public static Uri addImage(ContentResolver cr, String title, long dateAdded,
long dateTaken, Double latitude, Double longitude, String directory,
String filename, Bitmap source, byte[] jpegData, int[] degree) {
// We should store image data earlier than insert it to ContentProvider,
// otherwise we may not be able to generate thumbnail in time.
OutputStream outputStream = null;
String filePath = directory + "/" + filename;
try {
File dir = new File(directory);
if (!dir.exists()) dir.mkdirs();
File file = new File(directory, filename);
outputStream = new FileOutputStream(file);
if (source != null) {
source.compress(CompressFormat.JPEG, 75, outputStream);
degree[0] = 0;
} else {
outputStream.write(jpegData);
degree[0] = getExifOrientation(filePath);
}
} catch (FileNotFoundException ex) {
Log.w(TAG, ex);
return null;
} catch (IOException ex) {
Log.w(TAG, ex);
return null;
} finally {
Util.closeSilently(outputStream);
}
// Read back the compressed file size.
long size = new File(directory, filename).length();
ContentValues values = new ContentValues(11);
values.put(Images.Media.TITLE, title);
// That filename is what will be handed to Gmail when a user shares a
// photo. Gmail gets the name of the picture attachment from the
// "DISPLAY_NAME" field.
values.put(Images.Media.DISPLAY_NAME, filename);
values.put(Images.Media.DATE_TAKEN, dateTaken);
values.put(Images.Media.DATE_MODIFIED, dateTaken);
values.put(Images.Media.DATE_ADDED, dateAdded);
values.put(Images.Media.MIME_TYPE, "image/jpeg");
values.put(Images.Media.ORIENTATION, degree[0]);
values.put(Images.Media.DATA, filePath);
values.put(Images.Media.SIZE, size);
if (latitude != null && longitude != null) {
values.put(Images.Media.LATITUDE, latitude.floatValue());
values.put(Images.Media.LONGITUDE, longitude.floatValue());
}
return cr.insert(STORAGE_URI, values);
}
public static int getExifOrientation(String filepath) {
int degree = 0;
ExifInterface exif = null;
try {
exif = new ExifInterface(filepath);
} catch (IOException ex) {
Log.e(TAG, "cannot read exif", ex);
}
if (exif != null) {
int orientation = exif.getAttributeInt(
ExifInterface.TAG_ORIENTATION, -1);
if (orientation != -1) {
// We only recognize a subset of orientation tag values.
switch(orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
degree = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
degree = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
degree = 270;
break;
}
}
}
return degree;
}
private static class AddImageCancelable extends BaseCancelable<Void> {
private final Uri mUri;
private final ContentResolver mCr;
private final byte[] mJpegData;
public AddImageCancelable(Uri uri, ContentResolver cr, int orientation, Bitmap source, byte[] jpegData) {
if (source == null && jpegData == null || uri == null) {
throw new IllegalArgumentException("source cannot be null");
}
mUri = uri;
mCr = cr;
mJpegData = jpegData;
}
@Override
protected Void execute() throws InterruptedException, ExecutionException {
boolean complete = false;
try {
String[] projection = new String[] { ImageColumns._ID, ImageColumns.MINI_THUMB_MAGIC };
Cursor c = mCr.query(mUri, projection, null, null, null);
try {
c.moveToPosition(0);
} finally {
c.close();
}
ContentValues values = new ContentValues();
values.put(ImageColumns.MINI_THUMB_MAGIC, 0);
mCr.update(mUri, values, null, null);
OutputStream outputStream = null;
try {
outputStream = mCr.openOutputStream(mUri);
if (outputStream != null) {
outputStream.write(mJpegData);
}
} catch (IOException ex) {
// TODO: report error to caller
Log.e(TAG, "Cannot open file: " + mUri, ex);
} finally {
Util.closeSilently(outputStream);
}
complete = true;
return null;
} finally {
if (!complete) {
try {
mCr.delete(mUri, null, null);
} catch (Throwable t) {
// ignore it while clean up.
}
}
}
}
}
public static Cancelable<Void> storeImage(Uri uri, ContentResolver cr, int orientation, Bitmap source, byte[] jpegData) {
return new AddImageCancelable(uri, cr, orientation, source, jpegData);
}
static boolean isSingleImageMode(String uriString) {
return !uriString.startsWith(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString())
&& !uriString.startsWith(MediaStore.Images.Media.INTERNAL_CONTENT_URI.toString());
}
private static boolean checkFsWritable() {
// Create a temporary file to see whether a volume is really writeable.
// It's important not to put it in the root directory which may have a
// limit on the number of files.
String directoryName = Environment.getExternalStorageDirectory().toString() + "/DCIM";
File directory = new File(directoryName);
if (!directory.isDirectory()) {
if (!directory.mkdirs()) {
return false;
}
}
return directory.canWrite();
}
public static boolean quickHasStorage() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
public static boolean hasStorage() {
return hasStorage(true);
}
public static boolean hasStorage(boolean requireWriteAccess) {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
if (requireWriteAccess) {
boolean writable = checkFsWritable();
return writable;
} else {
return true;
}
} else if (!requireWriteAccess && Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}
private static final Cursor query(final ContentResolver resolver, final Uri uri, final String[] projection,
final String selection, final String[] selectionArgs, final String sortOrder) {
try {
if (resolver == null) {
return null;
}
return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
} catch (UnsupportedOperationException ex) {
return null;
}
}
public static final boolean isMediaScannerScanning(final ContentResolver cr) {
boolean result = false;
final Cursor cursor = query(cr, MediaStore.getMediaScannerUri(), new String[] { MediaStore.MEDIA_SCANNER_VOLUME }, null,
null, null);
if (cursor != null) {
if (cursor.getCount() == 1) {
cursor.moveToFirst();
result = "external".equals(cursor.getString(0));
}
cursor.close();
}
return result;
}
public static String getLastImageThumbPath() {
return Environment.getExternalStorageDirectory().toString() + "/DCIM/.thumbnails/image_last_thumb";
}
public static String getLastVideoThumbPath() {
return Environment.getExternalStorageDirectory().toString() + "/DCIM/.thumbnails/video_last_thumb";
}
}