/* * 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"; } }