diff options
Diffstat (limited to 'src/com/android/camera/ImageManager.java')
-rwxr-xr-x | src/com/android/camera/ImageManager.java | 4158 |
1 files changed, 303 insertions, 3855 deletions
diff --git a/src/com/android/camera/ImageManager.java b/src/com/android/camera/ImageManager.java index 073b8db..121852e 100755 --- a/src/com/android/camera/ImageManager.java +++ b/src/com/android/camera/ImageManager.java @@ -16,3633 +16,134 @@ package com.android.camera; -import android.content.Context; import android.content.ContentResolver; -import android.content.ContentValues; import android.content.ContentUris; -import android.database.ContentObserver; +import android.content.ContentValues; +import android.content.Context; import android.database.Cursor; -import android.database.DataSetObserver; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Matrix; import android.location.Location; -import android.media.MediaMetadataRetriever; -import android.media.MediaPlayer; import android.net.Uri; import android.os.Environment; import android.os.Handler; -import android.os.ParcelFileDescriptor; -import android.provider.BaseColumns; import android.provider.DrmStore; import android.provider.MediaStore; -import android.provider.MediaStore.Images.ImageColumns; -import android.provider.MediaStore.Images.Thumbnails; -import android.provider.MediaStore.Video.VideoColumns; import android.provider.MediaStore.Images; -import android.provider.MediaStore.MediaColumns; -import android.provider.MediaStore.Video; +import android.provider.MediaStore.Images.ImageColumns; import android.util.Config; import android.util.Log; +import com.android.camera.gallery.BaseCancelable; +import com.android.camera.gallery.BaseImageList; +import com.android.camera.gallery.CanceledException; +import com.android.camera.gallery.DrmImageList; +import com.android.camera.gallery.IAddImageCancelable; +import com.android.camera.gallery.IGetBooleanCancelable; +import com.android.camera.gallery.IImage; +import com.android.camera.gallery.IImageList; +import com.android.camera.gallery.Image; +import com.android.camera.gallery.ImageList; +import com.android.camera.gallery.ImageListUber; +import com.android.camera.gallery.SingleImageList; +import com.android.camera.gallery.Util; +import com.android.camera.gallery.VideoList; + import java.io.File; -import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.RandomAccessFile; import java.util.ArrayList; import java.util.HashMap; /** - * * ImageManager is used to retrieve and store images * in the media content provider. - * */ public class ImageManager { - 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"); - } - } - - // To enable verbose logging for this class, change false to true. The other logic ensures that - // this logging can be disabled by turned off DEBUG and lower, and that it can be enabled by - // "setprop log.tag.ImageManager VERBOSE" if desired. + // To enable verbose logging for this class, change false to true. The other + // logic ensures that this logging can be disabled by turned off DEBUG and + // lower, and that it can be enabled by "setprop log.tag.ImageManager + // VERBOSE" if desired. // // IMPORTANT: Never check in this file set to true! - private static final boolean VERBOSE = Config.LOGD && (false || Config.LOGV); + private static final boolean VERBOSE = + Config.LOGD && (false || Config.LOGV); private static final String TAG = "ImageManager"; + private static ImageManager sInstance = null; - private static final int MINI_THUMB_DATA_FILE_VERSION = 3; - - static public void debug_where(String tag, String msg) { - try { - throw new Exception(); - } catch (Exception ex) { - if (msg != null) { - Log.v(tag, msg); - } - boolean first = true; - for (StackTraceElement s : ex.getStackTrace()) { - if (first) - first = false; - else - Log.v(tag, s.toString()); - } - } - } - - /* - * Compute the sample size as a function of the image size and the target. - * Scale the image down so that both the width and height are just above - * the target. If this means that one of the dimension goes from above - * the target to below the target (e.g. given a width of 480 and an image - * width of 600 but sample size of 2 -- i.e. new width 300 -- bump the - * sample size down by 1. - */ - private static int computeSampleSize(BitmapFactory.Options options, int target) { - int w = options.outWidth; - int h = options.outHeight; - - int candidateW = w / target; - int candidateH = h / target; - int candidate = Math.max(candidateW, candidateH); - - if (candidate == 0) - return 1; - - if (candidate > 1) { - if ((w > target) && (w / candidate) < target) - candidate -= 1; - } - - if (candidate > 1) { - if ((h > target) && (h / candidate) < target) - candidate -= 1; - } - - if (VERBOSE) - Log.v(TAG, "for w/h " + w + "/" + h + " returning " + candidate + "(" + (w/candidate) + " / " + (h/candidate)); - - return candidate; - } - /* - * All implementors of ICancelable should inherit from BaseCancelable - * since it provides some convenience methods such as acknowledgeCancel - * and checkCancel. - */ - public abstract class BaseCancelable implements ICancelable { - boolean mCancel = false; - boolean mFinished = false; - - /* - * Subclasses should call acknowledgeCancel when they're finished with - * their operation. - */ - protected void acknowledgeCancel() { - synchronized (this) { - mFinished = true; - if (!mCancel) - return; - if (mCancel) { - this.notify(); - } - } - } - - public boolean cancel() { - synchronized (this) { - if (mCancel) { - return false; - } - if (mFinished) { - return false; - } - mCancel = true; - boolean retVal = doCancelWork(); - - try { - this.wait(); - } catch (InterruptedException ex) { - // now what??? TODO - } - - return retVal; - } - } - - /* - * Subclasses can call this to see if they have been canceled. - * This is the polling model. - */ - protected void checkCanceled() throws CanceledException { - synchronized (this) { - if (mCancel) - throw new CanceledException(); - } - } + private static Uri sStorageURI = Images.Media.EXTERNAL_CONTENT_URI; + private static Uri sThumbURI = Images.Thumbnails.EXTERNAL_CONTENT_URI; - /* - * Subclasses implement this method to take whatever action - * is necessary when getting canceled. Sometimes it's not - * possible to do anything in which case the "checkCanceled" - * polling model may be used (or some combination). - */ - public abstract boolean doCancelWork(); - } + private static Uri sVideoStorageURI = + Uri.parse("content://media/external/video/media"); - private static final int sBytesPerMiniThumb = 10000; - static final private byte [] sMiniThumbData = new byte[sBytesPerMiniThumb]; + private static Uri sVideoThumbURI = + Uri.parse("content://media/external/video/thumbnails"); /** - * Represents a particular image and provides access - * to the underlying bitmap and two thumbnail bitmaps - * as well as other information such as the id, and - * the path to the actual image data. - */ - abstract class BaseImage implements IImage { - protected ContentResolver mContentResolver; - protected long mId, mMiniThumbMagic; - protected BaseImageList mContainer; - protected HashMap<String, String> mExifData; - protected int mCursorRow; - - protected BaseImage(long id, long miniThumbId, ContentResolver cr, BaseImageList container, int cursorRow) { - mContentResolver = cr; - mId = id; - mMiniThumbMagic = miniThumbId; - mContainer = container; - mCursorRow = cursorRow; - } - - abstract Bitmap.CompressFormat compressionType(); - - public void commitChanges() { - Cursor c = getCursor(); - synchronized (c) { - if (c.moveToPosition(getRow())) { - c.commitUpdates(); - c.requery(); - } - } - } - - /** - * Take a given bitmap and compress it to a file as described - * by the Uri parameter. - * - * @param bitmap the bitmap to be compressed/stored - * @param uri where to store the bitmap - * @return true if we succeeded - */ - protected IGetBoolean_cancelable compressImageToFile( - final Bitmap bitmap, - final byte [] jpegData, - final Uri uri) { - class CompressImageToFile extends BaseCancelable implements IGetBoolean_cancelable { - ThreadSafeOutputStream mOutputStream = null; - - public boolean doCancelWork() { - if (mOutputStream != null) { - try { - mOutputStream.close(); - return true; - } catch (IOException ex) { - // TODO what to do here - } - } - return false; - } - - public boolean get() { - try { - long t1 = System.currentTimeMillis(); - OutputStream delegate = mContentResolver.openOutputStream(uri); - synchronized (this) { - checkCanceled(); - mOutputStream = new ThreadSafeOutputStream(delegate); - } - long t2 = System.currentTimeMillis(); - if (bitmap != null) { - bitmap.compress(compressionType(), 75, mOutputStream); - } else { - long x1 = System.currentTimeMillis(); - mOutputStream.write(jpegData); - long x2 = System.currentTimeMillis(); - if (VERBOSE) Log.v(TAG, "done writing... " + jpegData.length + " bytes took " + (x2-x1)); - } - long t3 = System.currentTimeMillis(); - if (VERBOSE) Log.v(TAG, String.format("CompressImageToFile.get took %d (%d, %d)",(t3-t1),(t2-t1),(t3-t2))); - return true; - } catch (FileNotFoundException ex) { - return false; - } catch (CanceledException ex) { - return false; - } catch (IOException ex) { - return false; - } - finally { - if (mOutputStream != null) { - try { - mOutputStream.close(); - } catch (IOException ex) { - // not much we can do here so ignore - } - } - acknowledgeCancel(); - } - } - } - return new CompressImageToFile(); - } - - @Override - public boolean equals(Object other) { - if (other == null) - return false; - if (!(other instanceof Image)) - return false; - - return fullSizeImageUri().equals(((Image)other).fullSizeImageUri()); - } - - public Bitmap fullSizeBitmap(int targetWidthHeight) { - return fullSizeBitmap(targetWidthHeight, true); - } - - protected Bitmap fullSizeBitmap(int targetWidthHeight, boolean rotateAsNeeded) { - Uri url = mContainer.contentUri(mId); - if (VERBOSE) Log.v(TAG, "getCreateBitmap for " + url); - if (url == null) - return null; - - Bitmap b = null; - if (b == null) { - b = makeBitmap(targetWidthHeight, url); - if (b != null && rotateAsNeeded) { - b = rotate(b, getDegreesRotated()); - } - } - return b; - } - - - public IGetBitmap_cancelable fullSizeBitmap_cancelable(final int targetWidthHeight) { - final class LoadBitmapCancelable extends BaseCancelable implements IGetBitmap_cancelable { - ParcelFileDescriptor mPFD; - BitmapFactory.Options mOptions = new BitmapFactory.Options(); - long mCancelInitiationTime; - - public LoadBitmapCancelable(ParcelFileDescriptor pfdInput) { - mPFD = pfdInput; - } - - public boolean doCancelWork() { - if (VERBOSE) - Log.v(TAG, "requesting bitmap load cancel"); - mCancelInitiationTime = System.currentTimeMillis(); - mOptions.requestCancelDecode(); - return true; - } - - public Bitmap get() { - try { - Bitmap b = makeBitmap(targetWidthHeight, fullSizeImageUri(), mPFD, mOptions); - if (mCancelInitiationTime != 0) { - if (VERBOSE) - Log.v(TAG, "cancelation of bitmap load success==" + (b == null ? "TRUE" : "FALSE") + " -- took " + (System.currentTimeMillis() - mCancelInitiationTime)); - } - if (b != null) { - b = rotate(b, getDegreesRotated()); - } - return b; - } catch (Exception ex) { - return null; - } finally { - acknowledgeCancel(); - } - } - } - - try { - ParcelFileDescriptor pfdInput = mContentResolver.openFileDescriptor(fullSizeImageUri(), "r"); - return new LoadBitmapCancelable(pfdInput); - } catch (FileNotFoundException ex) { - return null; - } catch (UnsupportedOperationException ex) { - return null; - } - } - - public InputStream fullSizeImageData() { - try { - InputStream input = mContentResolver.openInputStream( - fullSizeImageUri()); - return input; - } catch (IOException ex) { - return null; - } - } - - public long fullSizeImageId() { - return mId; - } - - public Uri fullSizeImageUri() { - return mContainer.contentUri(mId); - } - - public IImageList getContainer() { - return mContainer; - } - - Cursor getCursor() { - return mContainer.getCursor(); - } - - public long getDateTaken() { - if (mContainer.indexDateTaken() < 0) return 0; - Cursor c = getCursor(); - synchronized (c) { - c.moveToPosition(getRow()); - return c.getLong(mContainer.indexDateTaken()); - } - } - - protected int getDegreesRotated() { - return 0; - } - - public String getMimeType() { - if (mContainer.indexMimeType() < 0) { - Cursor c = null; - try { - c = mContentResolver.query( - fullSizeImageUri(), - new String[] { "_id", Images.Media.MIME_TYPE }, - null, - null, null); - if (c != null && c.moveToFirst()) { - return c.getString(1); - } else { - return ""; - } - } finally { - if (c != null) - c.close(); - } - } else { - String mimeType = null; - Cursor c = getCursor(); - synchronized(c) { - if (c.moveToPosition(getRow())) { - mimeType = c.getString(mContainer.indexMimeType()); - } - } - return mimeType; - } - } - - /* (non-Javadoc) - * @see com.android.camera.IImage#getDescription() - */ - public String getDescription() { - if (mContainer.indexDescription() < 0) { - Cursor c = null; - try { - c = mContentResolver.query( - fullSizeImageUri(), - new String[] { "_id", Images.Media.DESCRIPTION }, - null, - null, null); - if (c != null && c.moveToFirst()) { - return c.getString(1); - } else { - return ""; - } - } finally { - if (c != null) - c.close(); - } - } else { - String description = null; - Cursor c = getCursor(); - synchronized(c) { - if (c.moveToPosition(getRow())) { - description = c.getString(mContainer.indexDescription()); - } - } - return description; - } - } - - /* (non-Javadoc) - * @see com.android.camera.IImage#getIsPrivate() - */ - public boolean getIsPrivate() { - if (mContainer.indexPrivate() < 0) return false; - boolean isPrivate = false; - Cursor c = getCursor(); - synchronized(c) { - if (c.moveToPosition(getRow())) { - isPrivate = c.getInt(mContainer.indexPrivate()) != 0; - } - } - return isPrivate; - } - - public double getLatitude() { - if (mContainer.indexLatitude() < 0) return 0D; - Cursor c = getCursor(); - synchronized (c) { - c.moveToPosition(getRow()); - return c.getDouble(mContainer.indexLatitude()); - } - } - - public double getLongitude() { - if (mContainer.indexLongitude() < 0) return 0D; - Cursor c = getCursor(); - synchronized (c) { - c.moveToPosition(getRow()); - return c.getDouble(mContainer.indexLongitude()); - } - } - - /* (non-Javadoc) - * @see com.android.camera.IImage#getTitle() - */ - public String getTitle() { - String name = null; - Cursor c = getCursor(); - synchronized(c) { - if (c.moveToPosition(getRow())) { - if (mContainer.indexTitle() != -1) { - name = c.getString(mContainer.indexTitle()); - } - } - } - return name != null && name.length() > 0 ? name : String.valueOf(mId); - } - - /* (non-Javadoc) - * @see com.android.camera.IImage#getDisplayName() - */ - public String getDisplayName() { - if (mContainer.indexDisplayName() < 0) { - Cursor c = null; - try { - c = mContentResolver.query( - fullSizeImageUri(), - new String[] { "_id", Images.Media.DISPLAY_NAME }, - null, - null, null); - if (c != null && c.moveToFirst()) { - return c.getString(1); - } - } finally { - if (c != null) - c.close(); - } - } else { - String name = null; - Cursor c = getCursor(); - synchronized(c) { - if (c.moveToPosition(getRow())) { - name = c.getString(mContainer.indexDisplayName()); - } - } - if (name != null && name.length() > 0) - return name; - } - return String.valueOf(mId); - } - - public String getPicasaId() { - /* - if (mContainer.indexPicasaWeb() < 0) return null; - Cursor c = getCursor(); - synchronized (c) { - c.moveTo(getRow()); - return c.getString(mContainer.indexPicasaWeb()); - } - */ - return null; - } - - public int getRow() { - return mCursorRow; - } - - public int getWidth() { - ParcelFileDescriptor input = null; - try { - Uri uri = fullSizeImageUri(); - input = mContentResolver.openFileDescriptor(uri, "r"); - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeFileDescriptor(input.getFileDescriptor(), null, options); - return options.outWidth; - } catch (IOException ex) { - return 0; - } finally { - try { - if (input != null) { - input.close(); - } - } catch (IOException ex) { - } - } - } - - public int getHeight() { - ParcelFileDescriptor input = null; - try { - Uri uri = fullSizeImageUri(); - input = mContentResolver.openFileDescriptor(uri, "r"); - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeFileDescriptor(input.getFileDescriptor(), null, options); - return options.outHeight; - } catch (IOException ex) { - return 0; - } finally { - try { - if (input != null) { - input.close(); - } - } catch (IOException ex) { - } - } - } - - public boolean hasLatLong() { - if (mContainer.indexLatitude() < 0 || mContainer.indexLongitude() < 0) return false; - Cursor c = getCursor(); - synchronized (c) { - c.moveToPosition(getRow()); - return !c.isNull(mContainer.indexLatitude()) && !c.isNull(mContainer.indexLongitude()); - } - } - - /* (non-Javadoc) - * @see com.android.camera.IImage#imageId() - */ - public long imageId() { - return mId; - } - - /** - * Make a bitmap from a given Uri. - * - * @param uri - */ - private Bitmap makeBitmap(int targetWidthOrHeight, Uri uri) { - ParcelFileDescriptor input = null; - try { - input = mContentResolver.openFileDescriptor(uri, "r"); - return makeBitmap(targetWidthOrHeight, uri, input, null); - } catch (IOException ex) { - return null; - } finally { - try { - if (input != null) { - input.close(); - } - } catch (IOException ex) { - } - } - } - - protected Bitmap makeBitmap(int targetWidthHeight, Uri uri, ParcelFileDescriptor pfdInput, BitmapFactory.Options options) { - return mContainer.makeBitmap(targetWidthHeight, uri, pfdInput, options); - } - - /* (non-Javadoc) - * @see com.android.camera.IImage#thumb1() - */ - public Bitmap miniThumbBitmap() { - try { - long id = mId; - long dbMagic = mMiniThumbMagic; - if (dbMagic == 0 || dbMagic == id) { - dbMagic = ((BaseImageList)getContainer()).checkThumbnail(this, getCursor(), getRow()); - if (VERBOSE) Log.v(TAG, "after computing thumbnail dbMagic is " + dbMagic); - } - - synchronized(sMiniThumbData) { - dbMagic = mMiniThumbMagic; - byte [] data = mContainer.getMiniThumbFromFile(id, sMiniThumbData, dbMagic); - if (data == null) { - byte[][] createdThumbData = new byte[1][]; - try { - dbMagic = ((BaseImageList)getContainer()).checkThumbnail(this, getCursor(), - getRow(), createdThumbData); - } catch (IOException ex) { - // Typically IOException because the sd card is full. - // But createdThumbData may have been filled in, so continue on. - } - data = createdThumbData[0]; - } - if (data == null) { - data = mContainer.getMiniThumbFromFile(id, sMiniThumbData, dbMagic); - } - if (data == null) { - if (VERBOSE) - Log.v(TAG, "unable to get miniThumbBitmap, data is null"); - } - if (data != null) { - Bitmap b = BitmapFactory.decodeByteArray(data, 0, data.length); - if (b == null) { - if (VERBOSE) { - Log.v(TAG, "couldn't decode byte array for mini thumb, length was " + data.length); - } - } - return b; - } - } - return null; - } catch (Exception ex) { - // Typically IOException because the sd card is full. - if (VERBOSE) { - Log.e(TAG, "miniThumbBitmap got exception " + ex.toString()); - for (StackTraceElement s : ex.getStackTrace()) - Log.e(TAG, "... " + s.toString()); - } - return null; - } - } - - public void onRemove() { - mContainer.mCache.remove(mId); - } - - protected void saveMiniThumb(Bitmap source) throws IOException { - mContainer.saveMiniThumbToFile(source, fullSizeImageId(), 0); - } - - /* (non-Javadoc) - * @see com.android.camera.IImage#setName() - */ - public void setDescription(String description) { - if (mContainer.indexDescription() < 0) return; - Cursor c = getCursor(); - synchronized (c) { - if (c.moveToPosition(getRow())) { - c.updateString(mContainer.indexDescription(), description); - } - } - } - - /* (non-Javadoc) - * @see com.android.camera.IImage#setIsPrivate() - */ - public void setIsPrivate(boolean isPrivate) { - if (mContainer.indexPrivate() < 0) return; - Cursor c = getCursor(); - synchronized (c) { - if (c.moveToPosition(getRow())) { - c.updateInt(mContainer.indexPrivate(), isPrivate ? 1 : 0); - } - } - } - - /* (non-Javadoc) - * @see com.android.camera.IImage#setName() - */ - public void setName(String name) { - Cursor c = getCursor(); - synchronized (c) { - if (c.moveToPosition(getRow())) { - c.updateString(mContainer.indexTitle(), name); - } - } - } - - public void setPicasaId(String id) { - Cursor c = null; - try { - c = mContentResolver.query( - fullSizeImageUri(), - new String[] { "_id", Images.Media.PICASA_ID }, - null, - null, null); - if (c != null && c.moveToFirst()) { - if (VERBOSE) { - Log.v(TAG, "storing picasaid " + id + " for " + fullSizeImageUri()); - } - c.updateString(1, id); - c.commitUpdates(); - if (VERBOSE) { - Log.v(TAG, "updated image with picasa id " + id); - } - } - } finally { - if (c != null) - c.close(); - } - } - - /* (non-Javadoc) - * @see com.android.camera.IImage#thumbUri() - */ - public Uri thumbUri() { - Uri uri = fullSizeImageUri(); - // The value for the query parameter cannot be null :-(, so using a dummy "1" - uri = uri.buildUpon().appendQueryParameter("thumb", "1").build(); - return uri; - } - - @Override - public String toString() { - return fullSizeImageUri().toString(); - } - } - - abstract static class BaseImageList implements IImageList { - Context mContext; - ContentResolver mContentResolver; - Uri mBaseUri, mUri; - int mSort; - String mBucketId; - boolean mDistinct; - Cursor mCursor; - boolean mCursorDeactivated; - protected HashMap<Long, IImage> mCache = new HashMap<Long, IImage>(); - - IImageList.OnChange mListener = null; - Handler mHandler; - protected RandomAccessFile mMiniThumbData; - protected Uri mThumbUri; - - public BaseImageList(Context ctx, ContentResolver cr, Uri uri, int sort, String bucketId) { - mContext = ctx; - mSort = sort; - mUri = uri; - mBaseUri = uri; - mBucketId = bucketId; - - mContentResolver = cr; - } - - String randomAccessFilePath(int version) { - String directoryName = Environment.getExternalStorageDirectory().toString() + "/DCIM/.thumbnails"; - String path = directoryName + "/.thumbdata" + version + "-" + mUri.hashCode(); - return path; - } - - RandomAccessFile miniThumbDataFile() { - if (mMiniThumbData == null) { - String path = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION); - File directory = new File(new File(path).getParent()); - if (!directory.isDirectory()) { - if (!directory.mkdirs()) { - Log.e(TAG, "!!!! unable to create .thumbnails directory " + directory.toString()); - } - } - File f = new File(path); - if (VERBOSE) Log.v(TAG, "file f is " + f.toString()); - try { - mMiniThumbData = new RandomAccessFile(f, "rw"); - } catch (IOException ex) { - - } - } - return mMiniThumbData; - } - - /** - * Store a given thumbnail in the database. - */ - protected Bitmap storeThumbnail(Bitmap thumb, long imageId) { - if (thumb == null) - return null; - - try { - Uri uri = getThumbnailUri(imageId, thumb.getWidth(), thumb.getHeight()); - if (uri == null) { - return thumb; - } - OutputStream thumbOut = mContentResolver.openOutputStream(uri); - thumb.compress(Bitmap.CompressFormat.JPEG, 60, thumbOut); - thumbOut.close(); - return thumb; - } - catch (Exception ex) { - if (VERBOSE) Log.d(TAG, "unable to store thumbnail: " + ex); - return thumb; - } - } - - /** - * Store a JPEG thumbnail from the EXIF header in the database. - */ - protected boolean storeThumbnail(byte[] jpegThumbnail, long imageId, int width, int height) { - if (jpegThumbnail == null) - return false; - - Uri uri = getThumbnailUri(imageId, width, height); - if (uri == null) { - return false; - } - try { - OutputStream thumbOut = mContentResolver.openOutputStream(uri); - thumbOut.write(jpegThumbnail); - thumbOut.close(); - return true; - } - catch (FileNotFoundException ex) { - return false; - } - catch (IOException ex) { - return false; - } - } - - private Uri getThumbnailUri(long imageId, int width, int height) { - // we do not store thumbnails for DRM'd images - if (mThumbUri == null) { - return null; - } - - Uri uri = null; - Cursor c = null; - try { - c = mContentResolver.query( - mThumbUri, - THUMB_PROJECTION, - Thumbnails.IMAGE_ID + "=?", - new String[]{String.valueOf(imageId)}, - null); - if (c != null && c.moveToFirst()) { - // If, for some reaosn, we already have a row with a matching - // image id, then just update that row rather than creating a - // new row. - uri = ContentUris.withAppendedId(mThumbUri, c.getLong(indexThumbId())); - c.commitUpdates(); - } - } finally { - if (c != null) - c.close(); - } - if (uri == null) { - ContentValues values = new ContentValues(4); - values.put(Images.Thumbnails.KIND, Images.Thumbnails.MINI_KIND); - values.put(Images.Thumbnails.IMAGE_ID, imageId); - values.put(Images.Thumbnails.HEIGHT, height); - values.put(Images.Thumbnails.WIDTH, width); - uri = mContentResolver.insert(mThumbUri, values); - } - return uri; - } - - java.util.Random mRandom = new java.util.Random(System.currentTimeMillis()); - - protected SomewhatFairLock mLock = new SomewhatFairLock(); - - class SomewhatFairLock { - private Object mSync = new Object(); - private boolean mLocked = false; - private ArrayList<Thread> mWaiting = new ArrayList<Thread>(); - - void lock() { -// if (VERBOSE) Log.v(TAG, "lock... thread " + Thread.currentThread().getId()); - synchronized (mSync) { - while (mLocked) { - try { -// if (VERBOSE) Log.v(TAG, "waiting... thread " + Thread.currentThread().getId()); - mWaiting.add(Thread.currentThread()); - mSync.wait(); - if (mWaiting.get(0) == Thread.currentThread()) { - mWaiting.remove(0); - break; - } - } catch (InterruptedException ex) { - // - } - } -// if (VERBOSE) Log.v(TAG, "locked... thread " + Thread.currentThread().getId()); - mLocked = true; - } - } - - void unlock() { -// if (VERBOSE) Log.v(TAG, "unlocking... thread " + Thread.currentThread().getId()); - synchronized (mSync) { - mLocked = false; - mSync.notifyAll(); - } - } - } - - // If the photo has an EXIF thumbnail and it's big enough, extract it and save that JPEG as - // the large thumbnail without re-encoding it. We still have to decompress it though, in - // order to generate the minithumb. - private Bitmap createThumbnailFromEXIF(String filePath, long id) { - if (filePath != null) { - byte [] thumbData = null; - synchronized (ImageManager.instance()) { - thumbData = (new ExifInterface(filePath)).getThumbnail(); - } - if (thumbData != null) { - // Sniff the size of the EXIF thumbnail before decoding it. Photos from the - // device will pass, but images that are side loaded from other cameras may not. - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, options); - int width = options.outWidth; - int height = options.outHeight; - if (width >= THUMBNAIL_TARGET_SIZE && height >= THUMBNAIL_TARGET_SIZE) { - if (storeThumbnail(thumbData, id, width, height)) { - // this is used for *encoding* the minithumb, so - // we don't want to dither or convert to 565 here. - // - // Decode with a scaling factor - // to match MINI_THUMB_TARGET_SIZE closely - // which will produce much better scaling quality - // and is significantly faster. - options.inSampleSize = computeSampleSize(options, THUMBNAIL_TARGET_SIZE); - - if (VERBOSE) { - Log.v(TAG, "in createThumbnailFromExif using inSampleSize of " + options.inSampleSize); - } - options.inDither = false; - options.inPreferredConfig = Bitmap.Config.ARGB_8888; - options.inJustDecodeBounds = false; - return BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, options); - } - } - } - } - return null; - } - - // The fallback case is to decode the original photo to thumbnail size, then encode it as a - // JPEG. We return the thumbnail Bitmap in order to create the minithumb from it. - private Bitmap createThumbnailFromUri(Cursor c, long id) { - Uri uri = ContentUris.withAppendedId(mBaseUri, id); - Bitmap bitmap = makeBitmap(THUMBNAIL_TARGET_SIZE, uri, null, null); - if (bitmap != null) { - storeThumbnail(bitmap, id); - } else { - uri = ContentUris.withAppendedId(mBaseUri, id); - bitmap = makeBitmap(MINI_THUMB_TARGET_SIZE, uri, null, null); - } - return bitmap; - } - - // returns id - public long checkThumbnail(BaseImage existingImage, Cursor c, int i) throws IOException { - return checkThumbnail(existingImage, c, i, null); - } - - /** - * Checks to see if a mini thumbnail exists in the cache. If not, tries to create it and - * add it to the cache. - * @param existingImage - * @param c - * @param i - * @param createdThumbnailData if this parameter is non-null, and a new mini-thumbnail - * bitmap is created, the new bitmap's data will be stored in createdThumbnailData[0]. - * Note that if the sdcard is full, it's possible that - * createdThumbnailData[0] will be set even if the method throws an IOException. This is - * actually useful, because it allows the caller to use the created thumbnail even if - * the sdcard is full. - * @return - * @throws IOException - */ - public long checkThumbnail(BaseImage existingImage, Cursor c, int i, - byte[][] createdThumbnailData) throws IOException { - long magic, fileMagic = 0, id; - try { - mLock.lock(); - if (existingImage == null) { - // if we don't have an Image object then get the id and magic from - // the cursor. Synchronize on the cursor object. - synchronized (c) { - if (!c.moveToPosition(i)) { - return -1; - } - magic = c.getLong(indexMiniThumbId()); - id = c.getLong(indexId()); - } - } else { - // if we have an Image object then ask them for the magic/id - magic = existingImage.mMiniThumbMagic; - id = existingImage.fullSizeImageId(); - } - - if (magic != 0) { - // check the mini thumb file for the right data. Right is defined as - // having the right magic number at the offset reserved for this "id". - RandomAccessFile r = miniThumbDataFile(); - if (r != null) { - synchronized (r) { - long pos = id * sBytesPerMiniThumb; - try { - // check that we can read the following 9 bytes (1 for the "status" and 8 for the long) - if (r.length() >= pos + 1 + 8) { - r.seek(pos); - if (r.readByte() == 1) { - fileMagic = r.readLong(); - if (fileMagic == magic && magic != 0 && magic != id) { - return magic; - } - } - } - } catch (IOException ex) { - Log.v(TAG, "got exception checking file magic: " + ex); - } - } - } - if (VERBOSE) { - Log.v(TAG, "didn't verify... fileMagic: " + fileMagic + "; magic: " + magic + "; id: " + id + "; "); - } - } - - // If we can't retrieve the thumbnail, first check if there is one embedded in the - // EXIF data. If not, or it's not big enough, decompress the full size image. - Bitmap bitmap = null; - String filePath = null; - synchronized (c) { - if (c.moveToPosition(i)) { - filePath = c.getString(indexData()); - } - } - if (filePath != null) { - String mimeType = c.getString(indexMimeType()); - boolean isVideo = isVideoMimeType(mimeType); - if (isVideo) { - bitmap = createVideoThumbnail(filePath); - } else { - bitmap = createThumbnailFromEXIF(filePath, id); - if (bitmap == null) { - bitmap = createThumbnailFromUri(c, id); - } - } - synchronized (c) { - int degrees = 0; - if (c.moveToPosition(i)) { - int column = indexOrientation(); - if (column >= 0) - degrees = c.getInt(column); - } - if (degrees != 0) { - bitmap = rotate(bitmap, degrees); - } - } - } - - // make a new magic number since things are out of sync - do { - magic = mRandom.nextLong(); - } while (magic == 0); - if (bitmap != null) { - byte [] data = miniThumbData(bitmap); - if (createdThumbnailData != null) { - createdThumbnailData[0] = data; - } - saveMiniThumbToFile(data, id, magic); - } - - synchronized (c) { - c.moveToPosition(i); - c.updateLong(indexMiniThumbId(), magic); - c.commitUpdates(); - c.requery(); - c.moveToPosition(i); - - if (existingImage != null) { - existingImage.mMiniThumbMagic = magic; - } - return magic; - } - } finally { - mLock.unlock(); - } - } - - public void checkThumbnails(ThumbCheckCallback cb, int totalThumbnails) { - Cursor c = Images.Media.query( - mContentResolver, - mBaseUri, - new String[] { "_id", "mini_thumb_magic" }, - thumbnailWhereClause(), - thumbnailWhereClauseArgs(), - "_id ASC"); - - int count = c.getCount(); - if (VERBOSE) - Log.v(TAG, ">>>>>>>>>>> need to check " + c.getCount() + " rows"); - - c.close(); - - if (!ImageManager.hasStorage()) { - if (VERBOSE) - Log.v(TAG, "bailing from the image checker thread -- no storage"); - return; - } - - String oldPath = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION - 1); - File oldFile = new File(oldPath); - - if (count == 0) { - // now check that we have the right thumbs file -// Log.v(TAG, "count is zero but oldFile.exists() is " + oldFile.exists()); - if (!oldFile.exists()) { - return; - } - } - - c = getCursor(); - try { - if (VERBOSE) Log.v(TAG, "checkThumbnails found " + c.getCount()); - int current = 0; - for (int i = 0; i < c.getCount(); i++) { - try { - checkThumbnail(null, c, i); - } catch (Exception ex) { - Log.e(TAG, "!!!!! failed to check thumbnail... was the sd card removed?"); - break; - } - if (cb != null) { - if (!cb.checking(current, totalThumbnails)) { - if (VERBOSE) Log.v(TAG, "got false from checking... break <<<<<<<<<<<<<<<<<<<<<<<<"); - break; - } - } - current += 1; - } - } finally { - if (VERBOSE) Log.v(TAG, "checkThumbnails existing after reaching count " + c.getCount()); - try { - oldFile.delete(); - } catch (Exception ex) { - // ignore - } - } - } - - protected String thumbnailWhereClause() { - return sMiniThumbIsNull + " and " + sWhereClause; - } - - protected String[] thumbnailWhereClauseArgs() { - return sAcceptableImageTypes; - } - - public void commitChanges() { - synchronized (mCursor) { - mCursor.commitUpdates(); - requery(); - } - } - protected Uri contentUri(long id) { - try { - // does our uri already have an id (single image query)? - // if so just return it - long existingId = ContentUris.parseId(mBaseUri); - if (existingId != id) - Log.e(TAG, "id mismatch"); - return mBaseUri; - } catch (NumberFormatException ex) { - // otherwise tack on the id - return ContentUris.withAppendedId(mBaseUri, id); - } - } - - public void deactivate() { - mCursorDeactivated = true; - try { - mCursor.deactivate(); - } catch (IllegalStateException e) { - // IllegalStateException may be thrown if the cursor is stale. - Log.e(TAG, "Caught exception while deactivating cursor.", e); - } - if (mMiniThumbData != null) { - try { - mMiniThumbData.close(); - mMiniThumbData = null; - } catch (IOException ex) { - - } - } - } - - public void dump(String msg) { - int count = getCount(); - if (VERBOSE) Log.v(TAG, "dump ImageList (count is " + count + ") " + msg); - for (int i = 0; i < count; i++) { - IImage img = getImageAt(i); - if (img == null) - if (VERBOSE) Log.v(TAG, " " + i + ": " + "null"); - else - if (VERBOSE) Log.v(TAG, " " + i + ": " + img.toString()); - } - if (VERBOSE) Log.v(TAG, "end of dump container"); - } - public int getCount() { - Cursor c = getCursor(); - synchronized (c) { - try { - return c.getCount(); - } catch (Exception ex) { - } - return 0; - } - } - - public boolean isEmpty() { - return getCount() == 0; - } - - protected Cursor getCursor() { - synchronized (mCursor) { - if (mCursorDeactivated) { - activateCursor(); - } - return mCursor; - } - } - - protected void activateCursor() { - requery(); - } - - public IImage getImageAt(int i) { - Cursor c = getCursor(); - synchronized (c) { - boolean moved; - try { - moved = c.moveToPosition(i); - } catch (Exception ex) { - return null; - } - if (moved) { - try { - long id = c.getLong(0); - long miniThumbId = 0; - int rotation = 0; - if (indexMiniThumbId() != -1) { - miniThumbId = c.getLong(indexMiniThumbId()); - } - if (indexOrientation() != -1) { - rotation = c.getInt(indexOrientation()); - } - long timestamp = c.getLong(1); - IImage img = mCache.get(id); - if (img == null) { - img = make(id, miniThumbId, mContentResolver, this, timestamp, i, rotation); - mCache.put(id, img); - } - return img; - } catch (Exception ex) { - Log.e(TAG, "got this exception trying to create image object: " + ex); - return null; - } - } else { - Log.e(TAG, "unable to moveTo to " + i + "; count is " + c.getCount()); - return null; - } - } - } - public IImage getImageForUri(Uri uri) { - // TODO make this a hash lookup - for (int i = 0; i < getCount(); i++) { - if (getImageAt(i).fullSizeImageUri().equals(uri)) { - return getImageAt(i); - } - } - return null; - } - private byte [] getMiniThumbFromFile(long id, byte [] data, long magicCheck) { - RandomAccessFile r = miniThumbDataFile(); - if (r == null) - return null; - - long pos = id * sBytesPerMiniThumb; - RandomAccessFile f = r; - synchronized (f) { - try { - f.seek(pos); - if (f.readByte() == 1) { - long magic = f.readLong(); - if (magic != magicCheck) { - if (VERBOSE) Log.v(TAG, "for id " + id + "; magic: " + magic + "; magicCheck: " + magicCheck + " (fail)"); - return null; - } - int length = f.readInt(); - f.read(data, 0, length); - return data; - } else { - return null; - } - } catch (IOException ex) { - long fileLength; - try { - fileLength = f.length(); - } catch (IOException ex1) { - fileLength = -1; - } - if (VERBOSE) { - Log.e(TAG, "couldn't read thumbnail for " + id + "; " + ex.toString() + "; pos is " + pos + "; length is " + fileLength); - } - return null; - } - } - } - protected int getRowFor(IImage imageObj) { - Cursor c = getCursor(); - synchronized (c) { - int index = 0; - long targetId = imageObj.fullSizeImageId(); - if (c.moveToFirst()) { - do { - if (c.getLong(0) == targetId) { - return index; - } - index += 1; - } while (c.moveToNext()); - } - return -1; - } - } - - protected abstract int indexOrientation(); - protected abstract int indexDateTaken(); - protected abstract int indexDescription(); - protected abstract int indexMimeType(); - protected abstract int indexData(); - protected abstract int indexId(); - protected abstract int indexLatitude(); - protected abstract int indexLongitude(); - protected abstract int indexMiniThumbId(); - protected abstract int indexPicasaWeb(); - protected abstract int indexPrivate(); - protected abstract int indexTitle(); - protected abstract int indexDisplayName(); - protected abstract int indexThumbId(); - - protected IImage make(long id, long miniThumbId, ContentResolver cr, IImageList list, long timestamp, int index, int rotation) { - return null; - } - - protected abstract Bitmap makeBitmap(int targetWidthHeight, Uri uri, ParcelFileDescriptor pfdInput, BitmapFactory.Options options); - - public boolean removeImage(IImage image) { - Cursor c = getCursor(); - synchronized (c) { - /* - * TODO: consider putting the image in a holding area so - * we can get it back as needed - * TODO: need to delete the thumbnails as well - */ - boolean moved; - try { - moved = c.moveToPosition(image.getRow()); - } catch (Exception ex) { - Log.e(TAG, "removeImage got exception " + ex.toString()); - return false; - } - if (moved) { - Uri u = image.fullSizeImageUri(); - mContentResolver.delete(u, null, null); - image.onRemove(); - requery(); - } - } - return true; - } - - - /* (non-Javadoc) - * @see com.android.camera.IImageList#removeImageAt(int) - */ - public void removeImageAt(int i) { - Cursor c = getCursor(); - synchronized (c) { - /* - * TODO: consider putting the image in a holding area so - * we can get it back as needed - * TODO: need to delete the thumbnails as well - */ - dump("before delete"); - IImage image = getImageAt(i); - boolean moved; - try { - moved = c.moveToPosition(i); - } catch (Exception ex) { - return; - } - if (moved) { - Uri u = image.fullSizeImageUri(); - mContentResolver.delete(u, null, null); - requery(); - image.onRemove(); - } - dump("after delete"); - } - } - - public void removeOnChangeListener(OnChange changeCallback) { - if (changeCallback == mListener) - mListener = null; - } - - protected void requery() { - mCache.clear(); - mCursor.requery(); - mCursorDeactivated = false; - } - - protected void saveMiniThumbToFile(Bitmap bitmap, long id, long magic) throws IOException { - byte[] data = miniThumbData(bitmap); - saveMiniThumbToFile(data, id, magic); - } - - protected void saveMiniThumbToFile(byte[] data, long id, long magic) throws IOException { - RandomAccessFile r = miniThumbDataFile(); - if (r == null) - return; - - long pos = id * sBytesPerMiniThumb; - long t0 = System.currentTimeMillis(); - synchronized (r) { - try { - long t1 = System.currentTimeMillis(); - long t2 = System.currentTimeMillis(); - if (data != null) { - if (data.length > sBytesPerMiniThumb) { - if (VERBOSE) Log.v(TAG, "!!!!!!!!!!!!!!!!!!!!!!!!!!! " + data.length + " > " + sBytesPerMiniThumb); - return; - } - r.seek(pos); - r.writeByte(0); // we have no data in this slot - - // if magic is 0 then leave it alone - if (magic == 0) - r.skipBytes(8); - else - r.writeLong(magic); - r.writeInt(data.length); - r.write(data); - // f.flush(); - r.seek(pos); - r.writeByte(1); // we have data in this slot - long t3 = System.currentTimeMillis(); - - if (VERBOSE) Log.v(TAG, "saveMiniThumbToFile took " + (t3-t0) + "; " + (t1-t0) + " " + (t2-t1) + " " + (t3-t2)); - } - } catch (IOException ex) { - Log.e(TAG, "couldn't save mini thumbnail data for " + id + "; " + ex.toString()); - throw ex; - } - } - } - - public void setOnChangeListener(OnChange changeCallback, Handler h) { - mListener = changeCallback; - mHandler = h; - } - } - - public class CanceledException extends Exception { - - } - public enum DataLocation { NONE, INTERNAL, EXTERNAL, ALL } - - public interface IAddImage_cancelable extends ICancelable { - public void get(); - } - - /* - * The model for canceling an in-progress image save is this. For any - * given part of the task of saving return an ICancelable. The "result" - * from an ICancelable can be retrieved using the get* method. If the - * operation was canceled then null is returned. The act of canceling - * is to call "cancel" -- from another thread. - * - * In general an object which implements ICancelable will need to - * check, periodically, whether they are canceled or not. This works - * well for some things and less well for others. - * - * Right now the actual jpeg encode does not check cancelation but - * the part of encoding which writes the data to disk does. Note, - * though, that there is what appears to be a bug in the jpeg encoder - * in that if the stream that's being written is closed it crashes - * rather than returning an error. TODO fix that. - * - * When an object detects that it is canceling it must, before exiting, - * call acknowledgeCancel. This is necessary because the caller of - * cancel() will block until acknowledgeCancel is called. + * Enumerate type for the location of the images in gallery. */ - public interface ICancelable { - /* - * call cancel() when the unit of work in progress needs to be - * canceled. This should return true if it was possible to - * cancel and false otherwise. If this returns false the caller - * may still be able to cleanup and simulate cancelation. - */ - public boolean cancel(); - } - - public interface IGetBitmap_cancelable extends ICancelable { - // returns the bitmap or null if there was an error or we were canceled - public Bitmap get(); - }; - public interface IGetBoolean_cancelable extends ICancelable { - public boolean get(); - } - public interface IImage { - - public abstract void commitChanges(); - - /** - * Get the bitmap for the full size image. - * @return the bitmap for the full size image. - */ - public abstract Bitmap fullSizeBitmap(int targetWidthOrHeight); + public static enum DataLocation { NONE, INTERNAL, EXTERNAL, ALL } - /** - * - * @return an object which can be canceled while the bitmap is loading - */ - public abstract IGetBitmap_cancelable fullSizeBitmap_cancelable(int targetWidthOrHeight); + 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); - /** - * Gets the input stream associated with a given full size image. - * This is used, for example, if one wants to email or upload - * the image. - * @return the InputStream associated with the image. - */ - public abstract InputStream fullSizeImageData(); - public abstract long fullSizeImageId(); - public abstract Uri fullSizeImageUri(); - public abstract IImageList getContainer(); - public abstract long getDateTaken(); + public static final int SORT_ASCENDING = 1; + public static final int SORT_DESCENDING = 2; - /** - * Gets the description of the image. - * @return the description of the image. - */ - public abstract String getDescription(); - public abstract String getMimeType(); - public abstract int getHeight(); + 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); - /** - * Gets the flag telling whether this video/photo is private or public. - * @return the description of the image. - */ - public abstract boolean getIsPrivate(); - - public abstract double getLatitude(); - - public abstract double getLongitude(); - - /** - * Gets the name of the image. - * @return the name of the image. - */ - public abstract String getTitle(); - - public abstract String getDisplayName(); - - public abstract String getPicasaId(); - - public abstract int getRow(); - - public abstract int getWidth(); - - public abstract boolean hasLatLong(); - - public abstract long imageId(); - - public abstract boolean isReadonly(); - - public abstract boolean isDrm(); - - public abstract Bitmap miniThumbBitmap(); - - public abstract void onRemove(); - - public abstract boolean rotateImageBy(int degrees); - - /** - * Sets the description of the image. - */ - public abstract void setDescription(String description); - - /** - * Sets whether the video/photo is private or public. - */ - public abstract void setIsPrivate(boolean isPrivate); - - /** - * Sets the name of the image. - */ - public abstract void setName(String name); - - public abstract void setPicasaId(String id); - - /** - * Get the bitmap for the medium thumbnail. - * @return the bitmap for the medium thumbnail. - */ - public abstract Bitmap thumbBitmap(); - - public abstract Uri thumbUri(); - - public abstract String getDataPath(); - } - - public interface IImageList { - public HashMap<String, String> getBucketIds(); - - public interface OnChange { - public void onChange(IImageList list); - } - - public interface ThumbCheckCallback { - public boolean checking(int current, int count); - } - - public abstract void checkThumbnails(ThumbCheckCallback cb, int totalCount); - - public abstract void commitChanges(); - - public abstract void deactivate(); - - /** - * Returns the count of image objects. - * - * @return the number of images - */ - public abstract int getCount(); - - /** - * @return true if the count of image objects is zero. - */ - - public abstract boolean isEmpty(); - - /** - * Returns the image at the ith position. - * - * @param i the position - * @return the image at the ith position - */ - public abstract IImage getImageAt(int i); - - /** - * Returns the image with a particular Uri. - * - * @param uri - * @return the image with a particular Uri. - */ - public abstract IImage getImageForUri(Uri uri);; - - /** - * - * @param image - * @return true if the image was removed. - */ - public abstract boolean removeImage(IImage image); - /** - * Removes the image at the ith position. - * @param i the position - */ - public abstract void removeImageAt(int i); - - public abstract void removeOnChangeListener(OnChange changeCallback); - public abstract void setOnChangeListener(OnChange changeCallback, Handler h); - } - - class Image extends BaseImage implements IImage { - int mRotation; - - protected Image(long id, long miniThumbId, ContentResolver cr, BaseImageList container, int cursorRow, int rotation) { - super(id, miniThumbId, cr, container, cursorRow); - mRotation = rotation; - } - - public String getDataPath() { - String path = null; - Cursor c = getCursor(); - synchronized (c) { - if (c.moveToPosition(getRow())) { - int column = ((ImageList)getContainer()).indexData(); - if (column >= 0) - path = c.getString(column); - } - } - return path; - } - - protected int getDegreesRotated() { - return mRotation; - } - - protected void setDegreesRotated(int degrees) { - Cursor c = getCursor(); - mRotation = degrees; - synchronized (c) { - if (c.moveToPosition(getRow())) { - int column = ((ImageList)getContainer()).indexOrientation(); - if (column >= 0) { - c.updateInt(column, degrees); - getContainer().commitChanges(); - } - } - } - } - - protected Bitmap.CompressFormat compressionType() { - String mimeType = getMimeType(); - if (mimeType == null) - return Bitmap.CompressFormat.JPEG; - - if (mimeType.equals("image/png")) - return Bitmap.CompressFormat.PNG; - else if (mimeType.equals("image/gif")) - return Bitmap.CompressFormat.PNG; - - return Bitmap.CompressFormat.JPEG; - } - - /** - * Does not replace the tag if already there. Otherwise, adds to the exif tags. - * @param tag - * @param value - */ - public void addExifTag(String tag, String value) { - if (mExifData == null) { - mExifData = new HashMap<String, String>(); - } - if (!mExifData.containsKey(tag)) { - mExifData.put(tag, value); - } else { - if (VERBOSE) Log.v(TAG, "addExifTag where the key already was there: " + tag + " = " + value); - } - } - - /** - * Return the value of the Exif tag as an int. Returns 0 on any type of error. - * @param tag - * @return - */ - public int getExifTagInt(String tag) { - if (mExifData != null) { - String tagValue = mExifData.get(tag); - if (tagValue != null) { - return Integer.parseInt(tagValue); - } - } - return 0; - } - - public boolean isReadonly() { - String mimeType = getMimeType(); - return !"image/jpeg".equals(mimeType) && !"image/png".equals(mimeType); - } - - public boolean isDrm() { - return false; - } - - /** - * Remove tag if already there. Otherwise, does nothing. - * @param tag - */ - public void removeExifTag(String tag) { - if (mExifData == null) { - mExifData = new HashMap<String, String>(); - } - mExifData.remove(tag); - } - - /** - * Replaces the tag if already there. Otherwise, adds to the exif tags. - * @param tag - * @param value - */ - public void replaceExifTag(String tag, String value) { - if (mExifData == null) { - mExifData = new HashMap<String, String>(); - } - if (!mExifData.containsKey(tag)) { - mExifData.remove(tag); - } - mExifData.put(tag, value); - } - - /* (non-Javadoc) - * @see com.android.camera.IImage#saveModifiedImage(android.graphics.Bitmap) - */ - public IGetBoolean_cancelable saveImageContents( - final Bitmap image, - final byte [] jpegData, - final int orientation, - final boolean newFile, - final Cursor cursor) { - final class SaveImageContentsCancelable extends BaseCancelable implements IGetBoolean_cancelable { - IGetBoolean_cancelable mCurrentCancelable = null; - - SaveImageContentsCancelable() { - } - - public boolean doCancelWork() { - synchronized (this) { - if (mCurrentCancelable != null) - mCurrentCancelable.cancel(); - } - return true; - } - - public boolean get() { - try { - Bitmap thumbnail = null; - - long t1 = System.currentTimeMillis(); - Uri uri = mContainer.contentUri(mId); - synchronized (this) { - checkCanceled(); - mCurrentCancelable = compressImageToFile(image, jpegData, uri); - } - - long t2 = System.currentTimeMillis(); - if (!mCurrentCancelable.get()) - return false; - - synchronized (this) { - String filePath; - synchronized (cursor) { - cursor.moveToPosition(0); - filePath = cursor.getString(2); - } - // TODO: If thumbData is present and usable, we should call the version - // of storeThumbnail which takes a byte array, rather than re-encoding - // a new JPEG of the same dimensions. - byte [] thumbData = null; - synchronized (ImageManager.instance()) { - thumbData = (new ExifInterface(filePath)).getThumbnail(); - } - if (VERBOSE) Log.v(TAG, "for file " + filePath + " thumbData is " + thumbData + "; length " + (thumbData!=null ? thumbData.length : -1)); - if (thumbData != null) { - thumbnail = BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length); - if (VERBOSE) Log.v(TAG, "embedded thumbnail bitmap " + thumbnail.getWidth() + "/" + thumbnail.getHeight()); - } - if (thumbnail == null && image != null) { - thumbnail = image; - } - if (thumbnail == null && jpegData != null) { - thumbnail = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length); - } - } - - long t3 = System.currentTimeMillis(); - mContainer.storeThumbnail(thumbnail, Image.this.fullSizeImageId()); - long t4 = System.currentTimeMillis(); - checkCanceled(); - if (VERBOSE) Log.v(TAG, ">>>>>>>>>>>>>>>>>>>>> rotating by " + orientation); - try { - thumbnail = rotate(thumbnail, orientation); - saveMiniThumb(thumbnail); - } catch (IOException e) { - // Ignore if unable to save thumb. - } - long t5 = System.currentTimeMillis(); - checkCanceled(); - - if (VERBOSE) Log.v(TAG, String.format("Timing data %d %d %d %d", t2-t1, t3-t2, t4-t3, t5-t4)); - return true; - } catch (CanceledException ex) { - if (VERBOSE) Log.v(TAG, "got canceled... need to cleanup"); - return false; - } finally { - /* - Cursor c = getCursor(); - synchronized (c) { - if (c.moveTo(getRow())) { - mContainer.requery(); - } - } - */ - acknowledgeCancel(); - } - } - } - return new SaveImageContentsCancelable(); - } - - private void setExifRotation(int degrees) { - try { - Cursor c = getCursor(); - String filePath; - synchronized (c) { - filePath = c.getString(mContainer.indexData()); - } - synchronized (ImageManager.instance()) { - ExifInterface exif = new ExifInterface(filePath); - if (mExifData == null) { - mExifData = exif.getAttributes(); - } - if (degrees < 0) - degrees += 360; - - int orientation = ExifInterface.ORIENTATION_NORMAL; - switch (degrees) { - case 0: - orientation = ExifInterface.ORIENTATION_NORMAL; - break; - case 90: - orientation = ExifInterface.ORIENTATION_ROTATE_90; - break; - case 180: - orientation = ExifInterface.ORIENTATION_ROTATE_180; - break; - case 270: - orientation = ExifInterface.ORIENTATION_ROTATE_270; - break; - } - - replaceExifTag(ExifInterface.TAG_ORIENTATION, Integer.toString(orientation)); - replaceExifTag("UserComment", "saveRotatedImage comment orientation: " + orientation); - exif.saveAttributes(mExifData); - exif.commitChanges(); - } - } catch (Exception ex) { - Log.e(TAG, "unable to save exif data with new orientation " + fullSizeImageUri()); - } - } - - /** - * Save the rotated image by updating the Exif "Orientation" tag. - * @param degrees - * @return - */ - public boolean rotateImageBy(int degrees) { - int newDegrees = getDegreesRotated() + degrees; - setExifRotation(newDegrees); - setDegreesRotated(newDegrees); - - // setting this to zero will force the call to checkCursor to generate fresh thumbs - mMiniThumbMagic = 0; - try { - mContainer.checkThumbnail(this, mContainer.getCursor(), this.getRow()); - } catch (IOException e) { - // Ignore inability to store mini thumbnail. - } - - return true; - } - - public Bitmap thumbBitmap() { - Bitmap bitmap = null; - Cursor c = null; - if (mContainer.mThumbUri != null) { - try { - c = mContentResolver.query( - mContainer.mThumbUri, - THUMB_PROJECTION, - Thumbnails.IMAGE_ID + "=?", - new String[] { String.valueOf(fullSizeImageId()) }, - null); - if (c != null && c.moveToFirst()) { - Uri thumbUri = ContentUris.withAppendedId(mContainer.mThumbUri, c.getLong(((ImageList)mContainer).INDEX_THUMB_ID)); - ParcelFileDescriptor pfdInput; - try { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inDither = false; - options.inPreferredConfig = Bitmap.Config.ARGB_8888; - pfdInput = mContentResolver.openFileDescriptor(thumbUri, "r"); - bitmap = BitmapFactory.decodeFileDescriptor(pfdInput.getFileDescriptor(), null, options); - pfdInput.close(); - } catch (FileNotFoundException ex) { - Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex); - } catch (IOException ex) { - Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex); - } catch (NullPointerException ex) { - // we seem to get this if the file doesn't exist anymore - Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex); - } catch (OutOfMemoryError ex) { - Log.e(TAG, "failed to allocate memory for thumbnail " - + thumbUri + "; " + ex); - } - } - } catch (Exception ex) { - // sdcard removed? - return null; - } finally { - if (c != null) - c.close(); - } - } - - if (bitmap == null) { - bitmap = fullSizeBitmap(THUMBNAIL_TARGET_SIZE, false); - if (VERBOSE) { - Log.v(TAG, "no thumbnail found... storing new one for " + fullSizeImageId()); - } - bitmap = mContainer.storeThumbnail(bitmap, fullSizeImageId()); - } - - if (bitmap != null) { - bitmap = rotate(bitmap, getDegreesRotated()); - } - - long elapsed = System.currentTimeMillis(); - return bitmap; - } - - } - - final static private String sWhereClause = "(" + Images.Media.MIME_TYPE + " in (?, ?, ?))"; - final static private String[] sAcceptableImageTypes = new String[] { "image/jpeg", "image/png", "image/gif" }; - final static private String sMiniThumbIsNull = "mini_thumb_magic isnull"; + 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); - private static final String[] IMAGE_PROJECTION = new String[] { - "_id", - "_data", - ImageColumns.DATE_TAKEN, - ImageColumns.MINI_THUMB_MAGIC, - ImageColumns.ORIENTATION, - ImageColumns.MIME_TYPE - }; + public static final int MINI_THUMB_TARGET_SIZE = 96; + public static final int THUMBNAIL_TARGET_SIZE = 320; /** - * Represents an ordered collection of Image objects. - * Provides an api to add and remove an image. + * Matches code in MediaProvider.computeBucketValues. Should be a common + * function. */ - class ImageList extends BaseImageList implements IImageList { - final int INDEX_ID = indexOf(IMAGE_PROJECTION, "_id"); - final int INDEX_DATA = indexOf(IMAGE_PROJECTION, "_data"); - final int INDEX_MIME_TYPE = indexOf(IMAGE_PROJECTION, MediaColumns.MIME_TYPE); - final int INDEX_DATE_TAKEN = indexOf(IMAGE_PROJECTION, ImageColumns.DATE_TAKEN); - final int INDEX_MINI_THUMB_MAGIC = indexOf(IMAGE_PROJECTION, ImageColumns.MINI_THUMB_MAGIC); - final int INDEX_ORIENTATION = indexOf(IMAGE_PROJECTION, ImageColumns.ORIENTATION); - - final int INDEX_THUMB_ID = indexOf(THUMB_PROJECTION, BaseColumns._ID); - final int INDEX_THUMB_IMAGE_ID = indexOf(THUMB_PROJECTION, Images.Thumbnails.IMAGE_ID); - final int INDEX_THUMB_WIDTH = indexOf(THUMB_PROJECTION, Images.Thumbnails.WIDTH); - final int INDEX_THUMB_HEIGHT = indexOf(THUMB_PROJECTION, Images.Thumbnails.HEIGHT); - - boolean mIsRegistered = false; - ContentObserver mContentObserver; - DataSetObserver mDataSetObserver; - - public HashMap<String, String> getBucketIds() { - Cursor c = Images.Media.query( - mContentResolver, - mBaseUri.buildUpon().appendQueryParameter("distinct", "true").build(), - new String[] { - ImageColumns.BUCKET_DISPLAY_NAME, - ImageColumns.BUCKET_ID - }, - whereClause(), - whereClauseArgs(), - sortOrder()); - - HashMap<String, String> hash = new HashMap<String, String>(); - if (c != null && c.moveToFirst()) { - do { - hash.put(c.getString(1), c.getString(0)); - } while (c.moveToNext()); - } - return hash; - } - /** - * ImageList constructor. - * @param cr ContentResolver - */ - public ImageList(Context ctx, ContentResolver cr, Uri imageUri, Uri thumbUri, int sort, String bucketId) { - super(ctx, cr, imageUri, sort, bucketId); - mBaseUri = imageUri; - mThumbUri = thumbUri; - mSort = sort; - - mContentResolver = cr; - - mCursor = createCursor(); - if (mCursor == null) { - Log.e(TAG, "unable to create image cursor for " + mBaseUri); - throw new UnsupportedOperationException(); - } - - if (VERBOSE) { - Log.v(TAG, "for " + mBaseUri.toString() + " got cursor " + mCursor + " with length " + (mCursor != null ? mCursor.getCount() : "-1")); - } - - final Runnable updateRunnable = new Runnable() { - public void run() { - // handling these external updates is causing ANR problems that are unresolved. - // For now ignore them since there shouldn't be anyone modifying the database on the fly. - if (true) - return; - - synchronized (mCursor) { - requery(); - } - if (mListener != null) - mListener.onChange(ImageList.this); - } - }; - - mContentObserver = new ContentObserver(null) { - @Override - public boolean deliverSelfNotifications() { - return false; - } - - @Override - public void onChange(boolean selfChange) { - if (VERBOSE) Log.v(TAG, "MyContentObserver.onChange; selfChange == " + selfChange); - updateRunnable.run(); - } - }; - - mDataSetObserver = new DataSetObserver() { - @Override - public void onChanged() { - if (VERBOSE) Log.v(TAG, "MyDataSetObserver.onChanged"); -// updateRunnable.run(); - } - - @Override - public void onInvalidated() { - if (VERBOSE) Log.v(TAG, "MyDataSetObserver.onInvalidated: " + mCursorDeactivated); - } - }; - - registerObservers(); - } - - private void registerObservers() { - if (mIsRegistered) - return; - - mCursor.registerContentObserver(mContentObserver); - mCursor.registerDataSetObserver(mDataSetObserver); - mIsRegistered = true; - } - - private void unregisterObservers() { - if (!mIsRegistered) - return; - - mCursor.unregisterContentObserver(mContentObserver); - mCursor.unregisterDataSetObserver(mDataSetObserver); - mIsRegistered = false; - } - - public void deactivate() { - super.deactivate(); - unregisterObservers(); - } - - protected void activateCursor() { - super.activateCursor(); - registerObservers(); - } - - protected String whereClause() { - if (mBucketId != null) { - return sWhereClause + " and " + Images.Media.BUCKET_ID + " = '" + mBucketId + "'"; - } else { - return sWhereClause; - } - } - - protected String[] whereClauseArgs() { - return sAcceptableImageTypes; - } - - protected Cursor createCursor() { - Cursor c = - Images.Media.query( - mContentResolver, - mBaseUri, - IMAGE_PROJECTION, - whereClause(), - whereClauseArgs(), - sortOrder()); - if (VERBOSE) - Log.v(TAG, "createCursor got cursor with count " + (c == null ? -1 : c.getCount())); - return c; - } - - protected int indexOrientation() { return INDEX_ORIENTATION; } - protected int indexDateTaken() { return INDEX_DATE_TAKEN; } - protected int indexDescription() { return -1; } - protected int indexMimeType() { return INDEX_MIME_TYPE; } - protected int indexData() { return INDEX_DATA; } - protected int indexId() { return INDEX_ID; } - protected int indexLatitude() { return -1; } - protected int indexLongitude() { return -1; } - protected int indexMiniThumbId() { return INDEX_MINI_THUMB_MAGIC; } - - protected int indexPicasaWeb() { return -1; } - protected int indexPrivate() { return -1; } - protected int indexTitle() { return -1; } - protected int indexDisplayName() { return -1; } - protected int indexThumbId() { return INDEX_THUMB_ID; } - - @Override - protected IImage make(long id, long miniThumbId, ContentResolver cr, IImageList list, long timestamp, int index, int rotation) { - return new Image(id, miniThumbId, mContentResolver, this, index, rotation); - } - - protected Bitmap makeBitmap(int targetWidthHeight, Uri uri, ParcelFileDescriptor pfd, BitmapFactory.Options options) { - Bitmap b = null; - - try { - if (pfd == null) - pfd = makeInputStream(uri); - - if (pfd == null) - return null; - - if (options == null) - options = new BitmapFactory.Options(); - - java.io.FileDescriptor fd = pfd.getFileDescriptor(); - options.inSampleSize = 1; - if (targetWidthHeight != -1) { - options.inJustDecodeBounds = true; - long t1 = System.currentTimeMillis(); - BitmapFactory.decodeFileDescriptor(fd, null, options); - long t2 = System.currentTimeMillis(); - if (options.mCancel || options.outWidth == -1 || options.outHeight == -1) { - return null; - } - options.inSampleSize = computeSampleSize(options, targetWidthHeight); - options.inJustDecodeBounds = false; - } - - options.inDither = false; - options.inPreferredConfig = Bitmap.Config.ARGB_8888; - long t1 = System.currentTimeMillis(); - b = BitmapFactory.decodeFileDescriptor(fd, null, options); - long t2 = System.currentTimeMillis(); - if (VERBOSE) { - Log.v(TAG, "A: got bitmap " + b + " with sampleSize " + options.inSampleSize + " took " + (t2-t1)); - } - } catch (OutOfMemoryError ex) { - if (VERBOSE) Log.v(TAG, "got oom exception " + ex); - return null; - } finally { - try { - pfd.close(); - } catch (IOException ex) { - } - } - return b; - } - - private ParcelFileDescriptor makeInputStream(Uri uri) { - try { - return mContentResolver.openFileDescriptor(uri, "r"); - } catch (IOException ex) { - return null; - } - } - - private String sortOrder() { - // add id to the end so that we don't ever get random sorting - // which could happen, I suppose, if the first two values were - // duplicated - String ascending = (mSort == SORT_ASCENDING ? " ASC" : " DESC"); - return - Images.Media.DATE_TAKEN + ascending + "," + - Images.Media._ID + ascending; - } - + public static String getBucketId(String path) { + return String.valueOf(path.toLowerCase().hashCode()); } /** - * Represents an ordered collection of Image objects from the DRM provider. + * OSX requires plugged-in USB storage to have path /DCIM/NNNAAAAA to be + * imported. This is a temporary fix for bug#1655552. */ - class DrmImageList extends ImageList implements IImageList { - private final String[] DRM_IMAGE_PROJECTION = new String[] { - DrmStore.Audio._ID, - DrmStore.Audio.DATA, - DrmStore.Audio.MIME_TYPE, - }; - - final int INDEX_ID = indexOf(DRM_IMAGE_PROJECTION, DrmStore.Audio._ID); - final int INDEX_MIME_TYPE = indexOf(DRM_IMAGE_PROJECTION, DrmStore.Audio.MIME_TYPE); - - public DrmImageList(Context ctx, ContentResolver cr, Uri imageUri, int sort, String bucketId) { - super(ctx, cr, imageUri, null, sort, bucketId); - } - - protected Cursor createCursor() { - return mContentResolver.query(mBaseUri, DRM_IMAGE_PROJECTION, null, null, sortOrder()); - } - - @Override - public void checkThumbnails(ThumbCheckCallback cb, int totalCount) { - // do nothing - } - - @Override - public long checkThumbnail(BaseImage existingImage, Cursor c, int i) { - return 0; - } - - class DrmImage extends Image { - protected DrmImage(long id, ContentResolver cr, BaseImageList container, int cursorRow) { - super(id, 0, cr, container, cursorRow, 0); - } - - public boolean isDrm() { - return true; - } - - public boolean isReadonly() { - return true; - } - - public Bitmap miniThumbBitmap() { - return fullSizeBitmap(MINI_THUMB_TARGET_SIZE); - } - - public Bitmap thumbBitmap() { - return fullSizeBitmap(THUMBNAIL_TARGET_SIZE); - } - - public String getDisplayName() { - return getTitle(); - } - } - - @Override - protected IImage make(long id, long miniThumbId, ContentResolver cr, IImageList list, long timestamp, int index, int rotation) { - return new DrmImage(id, mContentResolver, this, index); - } - - protected int indexOrientation() { return -1; } - protected int indexDateTaken() { return -1; } - protected int indexDescription() { return -1; } - protected int indexMimeType() { return -1; } - protected int indexId() { return -1; } - protected int indexLatitude() { return -1; } - protected int indexLongitude() { return -1; } - protected int indexMiniThumbId() { return -1; } - protected int indexPicasaWeb() { return -1; } - protected int indexPrivate() { return -1; } - protected int indexTitle() { return -1; } - protected int indexDisplayName() { return -1; } - protected int indexThumbId() { return -1; } - - // TODO review this probably should be based on DATE_TAKEN same as images - private String sortOrder() { - String ascending = (mSort == SORT_ASCENDING ? " ASC" : " DESC"); - return - DrmStore.Images.TITLE + ascending + "," + - DrmStore.Images._ID; - } - } - - class ImageListUber implements IImageList { - private IImageList [] mSubList; - private int mSort; - private IImageList.OnChange mListener = null; - Handler mHandler; - - // This is an array of Longs wherein each Long consists of - // two components. The first component indicates the number of - // consecutive entries that belong to a given sublist. - // The second component indicates which sublist we're referring - // to (an int which is used to index into mSubList). - ArrayList<Long> mSkipList = null; - - int [] mSkipCounts = null; - - public HashMap<String, String> getBucketIds() { - HashMap<String, String> hashMap = new HashMap<String, String>(); - for (IImageList list: mSubList) { - hashMap.putAll(list.getBucketIds()); - } - return hashMap; - } - - public ImageListUber(IImageList [] sublist, int sort) { - mSubList = sublist.clone(); - mSort = sort; - - if (mListener != null) { - for (IImageList list: sublist) { - list.setOnChangeListener(new OnChange() { - public void onChange(IImageList list) { - if (mListener != null) { - mListener.onChange(ImageListUber.this); - } - } - }, mHandler); - } - } - } - - public void checkThumbnails(ThumbCheckCallback cb, int totalThumbnails) { - for (IImageList i : mSubList) { - int count = i.getCount(); - i.checkThumbnails(cb, totalThumbnails); - totalThumbnails -= count; - } - } - - public void commitChanges() { - final IImageList sublist[] = mSubList; - final int length = sublist.length; - for (int i = 0; i < length; i++) - sublist[i].commitChanges(); - } - - public void deactivate() { - final IImageList sublist[] = mSubList; - final int length = sublist.length; - int pos = -1; - while (++pos < length) { - IImageList sub = sublist[pos]; - sub.deactivate(); - } - } - - public int getCount() { - final IImageList sublist[] = mSubList; - final int length = sublist.length; - int count = 0; - for (int i = 0; i < length; i++) - count += sublist[i].getCount(); - return count; - } - - public boolean isEmpty() { - final IImageList sublist[] = mSubList; - final int length = sublist.length; - for (int i = 0; i < length; i++) { - if (! sublist[i].isEmpty()) { - return false; - } - } - return true; - } - - // mSkipCounts is used to tally the counts as we traverse - // the mSkipList. It's a member variable only so that - // we don't have to allocate each time through. Otherwise - // it could just as easily be a local. - - public synchronized IImage getImageAt(int index) { - if (index < 0 || index > getCount()) - throw new IndexOutOfBoundsException("index " + index + " out of range max is " + getCount()); - - // first make sure our allocations are in order - if (mSkipCounts == null || mSubList.length > mSkipCounts.length) - mSkipCounts = new int[mSubList.length]; - - if (mSkipList == null) - mSkipList = new ArrayList<Long>(); - - // zero out the mSkipCounts since that's only used for the - // duration of the function call - for (int i = 0; i < mSubList.length; i++) - mSkipCounts[i] = 0; - - // a counter of how many images we've skipped in - // trying to get to index. alternatively we could - // have decremented index but, alas, I liked this - // way more. - int skipCount = 0; - - // scan the existing mSkipList to see if we've computed - // enough to just return the answer - for (int i = 0; i < mSkipList.size(); i++) { - long v = mSkipList.get(i); - - int offset = (int) (v & 0xFFFF); - int which = (int) (v >> 32); - - if (skipCount + offset > index) { - int subindex = mSkipCounts[which] + (index - skipCount); - IImage img = mSubList[which].getImageAt(subindex); - return img; - } - - skipCount += offset; - mSkipCounts[which] += offset; - } - - // if we get here we haven't computed the answer for - // "index" yet so keep computing. This means running - // through the list of images and either modifying the - // last entry or creating a new one. - long count = 0; - while (true) { - long maxTimestamp = mSort == SORT_ASCENDING ? Long.MAX_VALUE : Long.MIN_VALUE; - int which = -1; - for (int i = 0; i < mSubList.length; i++) { - int pos = mSkipCounts[i]; - IImageList list = mSubList[i]; - if (pos < list.getCount()) { - IImage image = list.getImageAt(pos); - // this should never be null but sometimes the database is - // causing problems and it is null - if (image != null) { - long timestamp = image.getDateTaken(); - if (mSort == SORT_ASCENDING ? (timestamp < maxTimestamp) : (timestamp > maxTimestamp)) { - maxTimestamp = timestamp; - which = i; - } - } - } - } - - if (which == -1) { - if (VERBOSE) Log.v(TAG, "which is -1, returning null"); - return null; - } - - boolean done = false; - count = 1; - if (mSkipList.size() > 0) { - int pos = mSkipList.size() - 1; - long oldEntry = mSkipList.get(pos); - if ((oldEntry >> 32) == which) { - long newEntry = oldEntry + 1; - mSkipList.set(pos, newEntry); - done = true; - } - } - if (!done) { - long newEntry = ((long)which << 32) | count; - if (VERBOSE) { - Log.v(TAG, "new entry is " + Long.toHexString(newEntry)); - } - mSkipList.add(newEntry); - } - - if (skipCount++ == index) { - return mSubList[which].getImageAt(mSkipCounts[which]); - } - mSkipCounts[which] += 1; - } - } - - public IImage getImageForUri(Uri uri) { - // TODO perhaps we can preflight the base of the uri - // against each sublist first - for (int i = 0; i < mSubList.length; i++) { - IImage img = mSubList[i].getImageForUri(uri); - if (img != null) - return img; - } - return null; - } - - /** - * Modify the skip list when an image is deleted by finding - * the relevant entry in mSkipList and decrementing the - * counter. This is simple because deletion can never - * cause change the order of images. - */ - public void modifySkipCountForDeletedImage(int index) { - int skipCount = 0; - - for (int i = 0; i < mSkipList.size(); i++) { - long v = mSkipList.get(i); - - int offset = (int) (v & 0xFFFF); - int which = (int) (v >> 32); - - if (skipCount + offset > index) { - mSkipList.set(i, v-1); - break; - } - - skipCount += offset; - } - } - - public boolean removeImage(IImage image) { - IImageList parent = image.getContainer(); - int pos = -1; - int baseIndex = 0; - while (++pos < mSubList.length) { - IImageList sub = mSubList[pos]; - if (sub == parent) { - if (sub.removeImage(image)) { - modifySkipCountForDeletedImage(baseIndex); - return true; - } else { - break; - } - } - baseIndex += sub.getCount(); - } - return false; - } - - public void removeImageAt(int index) { - IImage img = getImageAt(index); - if (img != null) { - IImageList list = img.getContainer(); - if (list != null) { - list.removeImage(img); - modifySkipCountForDeletedImage(index); - } - } - } - - public void removeOnChangeListener(OnChange changeCallback) { - if (changeCallback == mListener) - mListener = null; - } - - public void setOnChangeListener(OnChange changeCallback, Handler h) { - mListener = changeCallback; - mHandler = h; - } - - } - - public static abstract class SimpleBaseImage implements IImage { - public void commitChanges() { - throw new UnsupportedOperationException(); - } - - public InputStream fullSizeImageData() { - throw new UnsupportedOperationException(); - } - - public long fullSizeImageId() { - return 0; - } - - public Uri fullSizeImageUri() { - throw new UnsupportedOperationException(); - } - - public IImageList getContainer() { - return null; - } - - public long getDateTaken() { - return 0; - } - - public String getMimeType() { - throw new UnsupportedOperationException(); - } - - public String getDescription() { - throw new UnsupportedOperationException(); - } - - public boolean getIsPrivate() { - throw new UnsupportedOperationException(); - } - - public double getLatitude() { - return 0D; - } - - public double getLongitude() { - return 0D; - } - - public String getTitle() { - throw new UnsupportedOperationException(); - } - - public String getDisplayName() { - throw new UnsupportedOperationException(); - } - - public String getPicasaId() { - return null; - } - - public int getRow() { - throw new UnsupportedOperationException(); - } - - public int getHeight() { - return 0; - } - - public int getWidth() { - return 0; - } - - public boolean hasLatLong() { - return false; - } - - public boolean isReadonly() { - return true; - } - - public boolean isDrm() { - return false; - } - - public void onRemove() { - throw new UnsupportedOperationException(); - } - - public boolean rotateImageBy(int degrees) { - return false; - } - - public void setDescription(String description) { - throw new UnsupportedOperationException(); - } - - public void setIsPrivate(boolean isPrivate) { - throw new UnsupportedOperationException(); - } - - public void setName(String name) { - throw new UnsupportedOperationException(); - } - - public void setPicasaId(long id) { - } - - public void setPicasaId(String id) { - } - - public Uri thumbUri() { - throw new UnsupportedOperationException(); - } - } - - class SingleImageList extends BaseImageList implements IImageList { - private IImage mSingleImage; - private ContentResolver mContentResolver; - private Uri mUri; - - class UriImage extends SimpleBaseImage { - - UriImage() { - } - - public String getDataPath() { - return mUri.getPath(); - } - - InputStream getInputStream() { - try { - if (mUri.getScheme().equals("file")) { - String path = mUri.getPath(); - if (VERBOSE) - Log.v(TAG, "path is " + path); - return new java.io.FileInputStream(mUri.getPath()); - } else { - return mContentResolver.openInputStream(mUri); - } - } catch (FileNotFoundException ex) { - return null; - } - } - - ParcelFileDescriptor getPFD() { - try { - if (mUri.getScheme().equals("file")) { - String path = mUri.getPath(); - if (VERBOSE) - Log.v(TAG, "path is " + path); - return ParcelFileDescriptor.open(new File(path), ParcelFileDescriptor.MODE_READ_ONLY); - } else { - return mContentResolver.openFileDescriptor(mUri, "r"); - } - } catch (FileNotFoundException ex) { - return null; - } - } - - /* (non-Javadoc) - * @see com.android.camera.ImageManager.IImage#fullSizeBitmap(int) - */ - public Bitmap fullSizeBitmap(int targetWidthHeight) { - try { - ParcelFileDescriptor pfdInput = getPFD(); - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeFileDescriptor(pfdInput.getFileDescriptor(), null, options); - - if (targetWidthHeight != -1) - options.inSampleSize = computeSampleSize(options, targetWidthHeight); - - options.inJustDecodeBounds = false; - options.inDither = false; - options.inPreferredConfig = Bitmap.Config.ARGB_8888; - - Bitmap b = BitmapFactory.decodeFileDescriptor(pfdInput.getFileDescriptor(), null, options); - if (VERBOSE) { - Log.v(TAG, "B: got bitmap " + b + " with sampleSize " + options.inSampleSize); - } - pfdInput.close(); - return b; - } catch (Exception ex) { - Log.e(TAG, "got exception decoding bitmap " + ex.toString()); - return null; - } - } - - public IGetBitmap_cancelable fullSizeBitmap_cancelable(final int targetWidthOrHeight) { - final class LoadBitmapCancelable extends BaseCancelable implements IGetBitmap_cancelable { - ParcelFileDescriptor pfdInput; - BitmapFactory.Options mOptions = new BitmapFactory.Options(); - long mCancelInitiationTime; - - public LoadBitmapCancelable(ParcelFileDescriptor pfd) { - pfdInput = pfd; - } - - public boolean doCancelWork() { - if (VERBOSE) - Log.v(TAG, "requesting bitmap load cancel"); - mCancelInitiationTime = System.currentTimeMillis(); - mOptions.requestCancelDecode(); - return true; - } - - public Bitmap get() { - try { - Bitmap b = makeBitmap(targetWidthOrHeight, fullSizeImageUri(), pfdInput, mOptions); - if (b == null && mCancelInitiationTime != 0) { - if (VERBOSE) - Log.v(TAG, "cancel returned null bitmap -- took " + (System.currentTimeMillis()-mCancelInitiationTime)); - } - if (VERBOSE) Log.v(TAG, "b is " + b); - return b; - } catch (Exception ex) { - return null; - } finally { - acknowledgeCancel(); - } - } - } - - try { - ParcelFileDescriptor pfdInput = getPFD(); - if (pfdInput == null) - return null; - if (VERBOSE) Log.v(TAG, "inputStream is " + pfdInput); - return new LoadBitmapCancelable(pfdInput); - } catch (UnsupportedOperationException ex) { - return null; - } - } - - @Override - public Uri fullSizeImageUri() { - return mUri; - } - - @Override - public InputStream fullSizeImageData() { - return getInputStream(); - } - - public long imageId() { - return 0; - } - - public Bitmap miniThumbBitmap() { - return thumbBitmap(); - } - - @Override - public String getTitle() { - return mUri.toString(); - } - - @Override - public String getDisplayName() { - return getTitle(); - } - - @Override - public String getDescription() { - return ""; - } - - public Bitmap thumbBitmap() { - Bitmap b = fullSizeBitmap(THUMBNAIL_TARGET_SIZE); - if (b != null) { - Matrix m = new Matrix(); - float scale = Math.min(1F, THUMBNAIL_TARGET_SIZE / (float) b.getWidth()); - m.setScale(scale, scale); - Bitmap scaledBitmap = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, true); - return scaledBitmap; - } else { - return null; - } - } - - private BitmapFactory.Options snifBitmapOptions() { - ParcelFileDescriptor input = getPFD(); - if (input == null) - return null; - try { - Uri uri = fullSizeImageUri(); - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeFileDescriptor(input.getFileDescriptor(), null, options); - return options; - } finally { - try { - if (input != null) { - input.close(); - } - } catch (IOException ex) { - } - } - } - - @Override - public String getMimeType() { - BitmapFactory.Options options = snifBitmapOptions(); - return (options!=null) ? options.outMimeType : ""; - } - - @Override - public int getHeight() { - BitmapFactory.Options options = snifBitmapOptions(); - return (options!=null) ? options.outHeight : 0; - } - - @Override - public int getWidth() { - BitmapFactory.Options options = snifBitmapOptions(); - return (options!=null) ? options.outWidth : 0; - } - } - - public SingleImageList(ContentResolver cr, Uri uri) { - super(null, cr, uri, ImageManager.SORT_ASCENDING, null); - mContentResolver = cr; - mUri = uri; - mSingleImage = new UriImage(); - } - - public HashMap<String, String> getBucketIds() { - throw new UnsupportedOperationException(); - } - - public void deactivate() { - // nothing to do here - } - - public int getCount() { - return 1; - } - - public boolean isEmpty() { - return false; - } - - public IImage getImageAt(int i) { - if (i == 0) - return mSingleImage; - - return null; - } - - public IImage getImageForUri(Uri uri) { - if (uri.equals(mUri)) - return mSingleImage; - else - return null; - } - - public IImage getImageWithId(long id) { - throw new UnsupportedOperationException(); - } - - @Override - protected int indexOrientation() { - return -1; - } - - @Override - protected int indexDateTaken() { - return -1; - } - - @Override - protected int indexMimeType() { - return -1; - } - - @Override - protected int indexDescription() { - return -1; - } - - @Override - protected int indexId() { - return -1; - } - - @Override - protected int indexData() { - return -1; - } - - @Override - protected int indexLatitude() { - return -1; - } - - @Override - protected int indexLongitude() { - return -1; - } - - @Override - protected int indexMiniThumbId() { - return -1; - } - - @Override - protected int indexPicasaWeb() { - return -1; - } - - @Override - protected int indexPrivate() { - return -1; - } - - @Override - protected int indexTitle() { - return -1; - } - - @Override - protected int indexDisplayName() { - return -1; - } - - @Override - protected int indexThumbId() { - return -1; - } - - private InputStream makeInputStream(Uri uri) { - InputStream input = null; - try { - input = mContentResolver.openInputStream(uri); - return input; - } catch (IOException ex) { - return null; - } - } - - @Override - protected Bitmap makeBitmap(int targetWidthHeight, Uri uri, ParcelFileDescriptor pfdInput, BitmapFactory.Options options) { - Bitmap b = null; - - try { - if (options == null) - options = new BitmapFactory.Options(); - options.inSampleSize = 1; - - if (targetWidthHeight != -1) { - options.inJustDecodeBounds = true; - BitmapFactory.decodeFileDescriptor(pfdInput.getFileDescriptor(), null, options); - - options.inSampleSize = computeSampleSize(options, targetWidthHeight); - options.inJustDecodeBounds = false; - } - b = BitmapFactory.decodeFileDescriptor(pfdInput.getFileDescriptor(), null, options); - if (VERBOSE) { - Log.v(TAG, "C: got bitmap " + b + " with sampleSize " + options.inSampleSize); - } - } catch (OutOfMemoryError ex) { - if (VERBOSE) Log.v(TAG, "got oom exception " + ex); - return null; - } finally { - try { - pfdInput.close(); - } catch (IOException ex) { - } - } - return b; - } - } - - class ThreadSafeOutputStream extends OutputStream { - java.io.OutputStream mDelegateStream; - boolean mClosed; - - public ThreadSafeOutputStream(OutputStream delegate) { - mDelegateStream = delegate; - } - - @Override - synchronized public void close() throws IOException { - try { - mClosed = true; - mDelegateStream.close(); - } catch (IOException ex) { - - } - } - - @Override - synchronized public void flush() throws IOException { - super.flush(); - } - - @Override - public void write(byte[] b, int offset, int length) throws IOException { - /* - mDelegateStream.write(b, offset, length); - return; - */ - while (length > 0) { - synchronized (this) { - if (mClosed) - return; - - int writeLength = Math.min(8192, length); - mDelegateStream.write(b, offset, writeLength); - offset += writeLength; - length -= writeLength; - } - } - } - - @Override - synchronized public void write(int oneByte) throws IOException { - if (mClosed) - return; - mDelegateStream.write(oneByte); + 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"); } } - class VideoList extends BaseImageList implements IImageList { - private final String[] sProjection = new String[] { - Video.Media._ID, - Video.Media.DATA, - Video.Media.DATE_TAKEN, - Video.Media.TITLE, - Video.Media.DISPLAY_NAME, - Video.Media.DESCRIPTION, - Video.Media.IS_PRIVATE, - Video.Media.TAGS, - Video.Media.CATEGORY, - Video.Media.LANGUAGE, - Video.Media.LATITUDE, - Video.Media.LONGITUDE, - Video.Media.MINI_THUMB_MAGIC, - Video.Media.MIME_TYPE, - }; - - final int INDEX_ID = indexOf(sProjection, Video.Media._ID); - final int INDEX_DATA = indexOf(sProjection, Video.Media.DATA); - final int INDEX_DATE_TAKEN = indexOf(sProjection, Video.Media.DATE_TAKEN); - final int INDEX_TITLE = indexOf(sProjection, Video.Media.TITLE); - final int INDEX_DISPLAY_NAME = indexOf(sProjection, Video.Media.DISPLAY_NAME); - final int INDEX_MIME_TYPE = indexOf(sProjection, Video.Media.MIME_TYPE); - final int INDEX_DESCRIPTION = indexOf(sProjection, Video.Media.DESCRIPTION); - final int INDEX_PRIVATE = indexOf(sProjection, Video.Media.IS_PRIVATE); - final int INDEX_TAGS = indexOf(sProjection, Video.Media.TAGS); - final int INDEX_CATEGORY = indexOf(sProjection, Video.Media.CATEGORY); - final int INDEX_LANGUAGE = indexOf(sProjection, Video.Media.LANGUAGE); - final int INDEX_LATITUDE = indexOf(sProjection, Video.Media.LATITUDE); - final int INDEX_LONGITUDE = indexOf(sProjection, Video.Media.LONGITUDE); - final int INDEX_MINI_THUMB_MAGIC = indexOf(sProjection, Video.Media.MINI_THUMB_MAGIC); - final int INDEX_THUMB_ID = indexOf(sProjection, BaseColumns._ID); - - public VideoList(Context ctx, ContentResolver cr, Uri uri, Uri thumbUri, - int sort, String bucketId) { - super(ctx, cr, uri, sort, bucketId); - - mCursor = createCursor(); - if (mCursor == null) { - Log.e(TAG, "unable to create video cursor for " + mBaseUri); - throw new UnsupportedOperationException(); - } - - if (Config.LOGV) { - Log.v(TAG, "for " + mUri.toString() + " got cursor " + mCursor + " with length " - + (mCursor != null ? mCursor.getCount() : -1)); - } - - if (mCursor == null) { - throw new UnsupportedOperationException(); - } - if (mCursor != null && mCursor.moveToFirst()) { - int row = 0; - do { - long imageId = mCursor.getLong(indexId()); - long dateTaken = mCursor.getLong(indexDateTaken()); - long miniThumbId = mCursor.getLong(indexMiniThumbId()); - mCache.put(imageId, new VideoObject(imageId, miniThumbId, mContentResolver, - this, dateTaken, row++)); - } while (mCursor.moveToNext()); - } + public static void debugWhere(String tag, String msg) { + Exception ex = new Exception(); + if (msg != null) { + Log.v(tag, msg); } - - public HashMap<String, String> getBucketIds() { - Cursor c = Images.Media.query( - mContentResolver, - mBaseUri.buildUpon().appendQueryParameter("distinct", "true").build(), - new String[] { - VideoColumns.BUCKET_DISPLAY_NAME, - VideoColumns.BUCKET_ID - }, - whereClause(), - whereClauseArgs(), - sortOrder()); - - HashMap<String, String> hash = new HashMap<String, String>(); - if (c != null && c.moveToFirst()) { - do { - hash.put(c.getString(1), c.getString(0)); - } while (c.moveToNext()); - } - return hash; - } - - protected String whereClause() { - if (mBucketId != null) { - return Images.Media.BUCKET_ID + " = '" + mBucketId + "'"; + boolean first = true; + for (StackTraceElement s : ex.getStackTrace()) { + if (first) { + first = false; } else { - return null; + Log.v(tag, s.toString()); } } - - protected String[] whereClauseArgs() { - return null; - } - - @Override - protected String thumbnailWhereClause() { - return sMiniThumbIsNull; - } - - @Override - protected String[] thumbnailWhereClauseArgs() { - return null; - } - - protected Cursor createCursor() { - Cursor c = - Images.Media.query( - mContentResolver, - mBaseUri, - sProjection, - whereClause(), - whereClauseArgs(), - sortOrder()); - if (VERBOSE) - Log.v(TAG, "createCursor got cursor with count " + (c == null ? -1 : c.getCount())); - return c; - } - - protected int indexOrientation() { return -1; } - protected int indexDateTaken() { return INDEX_DATE_TAKEN; } - protected int indexDescription() { return INDEX_DESCRIPTION; } - protected int indexMimeType() { return INDEX_MIME_TYPE; } - protected int indexData() { return INDEX_DATA; } - protected int indexId() { return INDEX_ID; } - protected int indexLatitude() { return INDEX_LATITUDE; } - protected int indexLongitude() { return INDEX_LONGITUDE; } - protected int indexMiniThumbId() { return INDEX_MINI_THUMB_MAGIC; } - protected int indexPicasaWeb() { return -1; } - protected int indexPrivate() { return INDEX_PRIVATE; } - protected int indexTitle() { return INDEX_TITLE; } - protected int indexDisplayName() { return -1; } - protected int indexThumbId() { return INDEX_THUMB_ID; } - - @Override - protected IImage make(long id, long miniThumbId, ContentResolver cr, IImageList list, - long timestamp, int index, int rotation) { - return new VideoObject(id, miniThumbId, mContentResolver, this, timestamp, index); - } - - @Override - protected Bitmap makeBitmap(int targetWidthHeight, Uri uri, ParcelFileDescriptor pfdInput, - BitmapFactory.Options options) { - MediaPlayer mp = new MediaPlayer(); - Bitmap thumbnail = sDefaultThumbnail; - try { - mp.setDataSource(mContext, uri); -// int duration = mp.getDuration(); -// int at = duration > 2000 ? 1000 : duration / 2; - int at = 1000; - thumbnail = mp.getFrameAt(at); - if (Config.LOGV) { - if ( thumbnail != null) { - Log.v(TAG, "getFrameAt @ " + at + " returned " + thumbnail + "; " + - thumbnail.getWidth() + " " + thumbnail.getHeight()); - } else { - Log.v(TAG, "getFrame @ " + at + " failed for " + uri); - } - } - } catch (IOException ex) { - } catch (IllegalArgumentException ex) { - } catch (SecurityException ex) { - } finally { - mp.release(); - } - return thumbnail; - } - - - private String sortOrder() { - return Video.Media.DATE_TAKEN + (mSort == SORT_ASCENDING ? " ASC " : " DESC"); - } } - private final static Bitmap sDefaultThumbnail = Bitmap.createBitmap(32, 32, Bitmap.Config.RGB_565); - - /** - * Represents a particular video and provides access - * to the underlying data and two thumbnail bitmaps - * as well as other information such as the id, and - * the path to the actual video data. - */ - class VideoObject extends BaseImage implements IImage { - /** - * Constructor. - * - * @param id the image id of the image - * @param cr the content resolver - */ - protected VideoObject(long id, long miniThumbId, ContentResolver cr, VideoList container, - long dateTaken, int row) { - super(id, miniThumbId, cr, container, row); - } - - protected Bitmap.CompressFormat compressionType() { - return Bitmap.CompressFormat.JPEG; - } - - @Override - public boolean equals(Object other) { - if (other == null) - return false; - if (!(other instanceof VideoObject)) - return false; - - return fullSizeImageUri().equals(((VideoObject)other).fullSizeImageUri()); - } - - public String getDataPath() { - String path = null; - Cursor c = getCursor(); - synchronized (c) { - if (c.moveToPosition(getRow())) { - int column = ((VideoList)getContainer()).indexData(); - if (column >= 0) - path = c.getString(column); - } - } - return path; - } - - /* (non-Javadoc) - * @see com.android.camera.IImage#fullSizeBitmap() - */ - public Bitmap fullSizeBitmap(int targetWidthHeight) { - return sNoImageBitmap; - } - - public IGetBitmap_cancelable fullSizeBitmap_cancelable(int targetWidthHeight) { - return null; - } - - /* (non-Javadoc) - * @see com.android.camera.IImage#fullSizeImageData() - */ - public InputStream fullSizeImageData() { - try { - InputStream input = mContentResolver.openInputStream( - fullSizeImageUri()); - return input; - } catch (IOException ex) { - return null; - } - } - - /* (non-Javadoc) - * @see com.android.camera.IImage#fullSizeImageId() - */ - public long fullSizeImageId() { - return mId; - } - - public String getCategory() { - return getStringEntry(((VideoList)mContainer).INDEX_CATEGORY); - } - - public int getHeight() { - return 0; - } - - public String getLanguage() { - return getStringEntry(((VideoList)mContainer).INDEX_LANGUAGE); - } - - public String getPicasaId() { - return null; - } - - private String getStringEntry(int entryName) { - String entry = null; - Cursor c = getCursor(); - synchronized(c) { - if (c.moveToPosition(getRow())) { - entry = c.getString(entryName); - } - } - return entry; - } - - public String getTags() { - return getStringEntry(((VideoList)mContainer).INDEX_TAGS); - } - - public int getWidth() { - return 0; - } - - /* (non-Javadoc) - * @see com.android.camera.IImage#imageId() - */ - public long imageId() { - return mId; - } - - public boolean isReadonly() { - return false; - } - - public boolean isDrm() { - return false; - } - - public boolean rotateImageBy(int degrees) { - return false; - } - - public void setCategory(String category) { - setStringEntry(category, ((VideoList)mContainer).INDEX_CATEGORY); - } - - public void setLanguage(String language) { - setStringEntry(language, ((VideoList)mContainer).INDEX_LANGUAGE); - } - - private void setStringEntry(String entry, int entryName) { - Cursor c = getCursor(); - synchronized (c) { - if (c.moveToPosition(getRow())) { - c.updateString(entryName, entry); - } - } - } - - public void setTags(String tags) { - setStringEntry(tags, ((VideoList)mContainer).INDEX_TAGS); - } - - /* (non-Javadoc) - * @see com.android.camera.IImage#thumb1() - */ - public Bitmap thumbBitmap() { - return fullSizeBitmap(320); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("" + mId); - return sb.toString(); - } - } - - private final static Bitmap sNoImageBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565); - - /* - * How much quality to use when storing the thumbnail. - */ - private static ImageManager sInstance = null; - private static final int MINI_THUMB_TARGET_SIZE = 96; - private static final int THUMBNAIL_TARGET_SIZE = 320; - - private static final String[] THUMB_PROJECTION = new String[] { - BaseColumns._ID, // 0 - Images.Thumbnails.IMAGE_ID, // 1 - Images.Thumbnails.WIDTH, - Images.Thumbnails.HEIGHT - }; - - private static Uri sStorageURI = Images.Media.EXTERNAL_CONTENT_URI; - - private static Uri sThumbURI = Images.Thumbnails.EXTERNAL_CONTENT_URI; - - private static Uri sVideoStorageURI = Uri.parse("content://media/external/video/media"); - - private static Uri sVideoThumbURI = Uri.parse("content://media/external/video/thumbnails"); - /** - * Returns an ImageList object that contains - * all of the images. - * @param cr - * @param location - * @param includeImages - * @param includeVideo - * @return the singleton ImageList - */ - static final public int SORT_ASCENDING = 1; - - static final public int SORT_DESCENDING = 2; - - static final public int INCLUDE_IMAGES = (1 << 0); - static final public int INCLUDE_DRM_IMAGES = (1 << 1); - static final public int INCLUDE_VIDEOS = (1 << 2); - - static public DataLocation getDefaultDataLocation() { + public static DataLocation getDefaultDataLocation() { return DataLocation.EXTERNAL; } - private static int indexOf(String [] array, String s) { - for (int i = 0; i < array.length; i++) { - if (array[i].equals(s)) { - return i; - } - } - return -1; - } - /** * Returns the singleton instance of the ImageManager. * @return the ImageManager instance. @@ -3654,105 +155,32 @@ public class ImageManager { return sInstance; } - /** - * Creates a byte[] for a given bitmap of the desired size. Recycles the input bitmap. - */ - static public byte[] miniThumbData(Bitmap source) { - if (source == null) - return null; - - Bitmap miniThumbnail = extractMiniThumb(source, MINI_THUMB_TARGET_SIZE, - MINI_THUMB_TARGET_SIZE); - java.io.ByteArrayOutputStream miniOutStream = new java.io.ByteArrayOutputStream(); - miniThumbnail.compress(Bitmap.CompressFormat.JPEG, 75, miniOutStream); - miniThumbnail.recycle(); - - try { - miniOutStream.close(); - byte [] data = miniOutStream.toByteArray(); - return data; - } catch (java.io.IOException ex) { - Log.e(TAG, "got exception ex " + ex); - } - return null; - } - - /** - * Creates a centered bitmap of the desired size. Recycles the input. - * @param source - * @return - */ - static public Bitmap extractMiniThumb(Bitmap source, int width, int height) { - return extractMiniThumb(source, width, height, true); - } - - static public Bitmap extractMiniThumb(Bitmap source, int width, int height, - boolean recycle) { - if (source == null) { - return null; - } - - float scale; - if (source.getWidth() < source.getHeight()) { - scale = width / (float)source.getWidth(); - } else { - scale = height / (float)source.getHeight(); - } - Matrix matrix = new Matrix(); - matrix.setScale(scale, scale); - Bitmap miniThumbnail = ImageLoader.transform(matrix, source, - width, height, false); - - if (recycle && miniThumbnail != source) { - source.recycle(); - } - return miniThumbnail; - } - - // Rotates the bitmap by the specified degree. - // If a new bitmap is created, the original bitmap is recycled. - static Bitmap rotate(Bitmap b, int degrees) { - if (degrees != 0 && b != null) { - Matrix m = new Matrix(); - m.setRotate(degrees, (float) b.getWidth() / 2, (float) b.getHeight() / 2); - - try { - Bitmap b2 = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, true); - if (b != b2) { - b.recycle(); - b = b2; - } - } catch (OutOfMemoryError ex) { - // We have no memory to rotate. Return the original bitmap. - } - } - return b; - } - public static int roundOrientation(int orientationInput) { int orientation = orientationInput; - if (orientation == -1) + if (orientation == -1) { orientation = 0; + } orientation = orientation % 360; int retVal; - if (orientation < (0*90) + 45) { + if (orientation < (0 * 90) + 45) { retVal = 0; - } else if (orientation < (1*90) + 45) { + } else if (orientation < (1 * 90) + 45) { retVal = 90; - } else if (orientation < (2*90) + 45) { + } else if (orientation < (2 * 90) + 45) { retVal = 180; - } else if (orientation < (3*90) + 45) { + } else if (orientation < (3 * 90) + 45) { retVal = 270; } else { retVal = 0; } - if (VERBOSE) Log.v(TAG, "map orientation " + orientationInput + " to " + retVal); + if (VERBOSE) { + Log.v(TAG, "map orientation " + orientationInput + " to " + retVal); + } return retVal; } - /** * @return true if the mimetype is an image mimetype. */ @@ -3761,13 +189,6 @@ public class ImageManager { } /** - * @return true if the mimetype is a video mimetype. - */ - public static boolean isVideoMimeType(String mimeType) { - return mimeType.startsWith("video/"); - } - - /** * @return true if the image is an image. */ public static boolean isImage(IImage image) { @@ -3778,19 +199,13 @@ public class ImageManager { * @return true if the image is a video. */ public static boolean isVideo(IImage image) { - return isVideoMimeType(image.getMimeType()); + return Util.isVideoMimeType(image.getMimeType()); } - public Uri addImage( - final Context ctx, - final ContentResolver cr, - final String imageName, - final String description, - final long dateTaken, - final Location location, - final int orientation, - final String directory, - final String filename) { + public Uri addImage(Context ctx, ContentResolver cr, String imageName, + String description, long dateTaken, Location location, + int orientation, String directory, String filename) { + ContentValues values = new ContentValues(7); values.put(Images.Media.TITLE, imageName); values.put(Images.Media.DISPLAY_NAME, imageName); @@ -3800,17 +215,22 @@ public class ImageManager { values.put(Images.Media.ORIENTATION, orientation); File parentFile = new File(directory); - // Lowercase the path for hashing. This avoids duplicate buckets if the filepath - // case is changed externally. + + // Lowercase the path for hashing. This avoids duplicate buckets if the + // filepath case is changed externally. // Keep the original case for display. String path = parentFile.toString().toLowerCase(); String name = parentFile.getName(); - if (VERBOSE) Log.v(TAG, "addImage id is " + path.hashCode() + "; name " + name + "; path is " + path); + if (VERBOSE) { + Log.v(TAG, "addImage id is " + path.hashCode() + "; name " + + name + "; path is " + path); + } if (location != null) { if (VERBOSE) { - Log.v(TAG, "lat long " + location.getLatitude() + " / " + location.getLongitude()); + Log.v(TAG, "lat long " + location.getLatitude() + " / " + + location.getLongitude()); } values.put(Images.Media.LATITUDE, location.getLatitude()); values.put(Images.Media.LONGITUDE, location.getLongitude()); @@ -3825,21 +245,21 @@ public class ImageManager { Uri uri = cr.insert(sStorageURI, values); // The line above will create a filename that ends in .jpg - // 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. - // Extract the filename and jam it into the display name. - Cursor c = cr.query( - uri, - new String [] { ImageColumns._ID, Images.Media.DISPLAY_NAME, "_data" }, - null, - null, - null); + // 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. Extract the filename and jam it into the + // display name. + String projection[] = new String [] { + ImageColumns._ID, Images.Media.DISPLAY_NAME, "_data"}; + Cursor c = cr.query(uri, projection, null, null, null); + if (c.moveToFirst()) { String filePath = c.getString(2); if (filePath != null) { int pos = filePath.lastIndexOf("/"); if (pos >= 0) { - filePath = filePath.substring(pos + 1); // pick off the filename + // pick off the filename + filePath = filePath.substring(pos + 1); c.updateString(1, filePath); c.commitUpdates(); } @@ -3849,181 +269,209 @@ public class ImageManager { return uri; } - public IAddImage_cancelable storeImage( - final Uri uri, - final Context ctx, - final ContentResolver cr, - final int orientation, - final Bitmap source, - final byte [] jpegData) { - class AddImageCancelable extends BaseCancelable implements IAddImage_cancelable { - private IGetBoolean_cancelable mSaveImageCancelable; + private static class AddImageCancelable extends BaseCancelable + implements IAddImageCancelable { + private IGetBooleanCancelable mSaveImageCancelable; + private Uri mUri; + private Context mCtx; + private ContentResolver mCr; + private int mOrientation; + private Bitmap mSource; + private byte [] mJpegData; + + public AddImageCancelable(Uri uri, Context ctx, ContentResolver cr, + int orientation, Bitmap source, byte[] jpegData) { + mUri = uri; + mCtx = ctx; + mCr = cr; + mOrientation = orientation; + mSource = source; + mJpegData = jpegData; + } - public boolean doCancelWork() { - if (VERBOSE) { - Log.v(TAG, "calling AddImageCancelable.cancel() " + mSaveImageCancelable); - } + @Override + public boolean doCancelWork() { + if (VERBOSE) { + Log.v(TAG, "calling AddImageCancelable.cancel() " + + mSaveImageCancelable); + } + if (mSaveImageCancelable != null) { + mSaveImageCancelable.cancel(); + } + return true; + } - if (mSaveImageCancelable != null) { - mSaveImageCancelable.cancel(); - } - return true; + public void get() { + if (mSource == null && mJpegData == null) { + throw new IllegalArgumentException("source cannot be null"); } - public void get() { - if (source == null && jpegData == null) { - throw new IllegalArgumentException("source cannot be null"); + try { + long t1 = System.currentTimeMillis(); + synchronized (this) { + if (mCancel) { + throw new CanceledException(); + } } + long id = ContentUris.parseId(mUri); - try { - long t1 = System.currentTimeMillis(); - synchronized (this) { - if (mCancel) { - throw new CanceledException(); - } - } - long id = ContentUris.parseId(uri); + BaseImageList il = new ImageList(mCtx, mCr, sStorageURI, + sThumbURI, SORT_ASCENDING, null); + Image image = new Image(id, 0, mCr, il, il.getCount(), 0); + long t5 = System.currentTimeMillis(); + String[] projection = new String[] { + ImageColumns._ID, + ImageColumns.MINI_THUMB_MAGIC, "_data"}; - BaseImageList il = new ImageList(ctx, cr, sStorageURI, sThumbURI, SORT_ASCENDING, null); - ImageManager.Image image = new Image(id, 0, cr, il, il.getCount(), 0); - long t5 = System.currentTimeMillis(); - Cursor c = cr.query( - uri, - new String [] { ImageColumns._ID, ImageColumns.MINI_THUMB_MAGIC, "_data" }, - null, - null, - null); - c.moveToPosition(0); + Cursor c = mCr.query(mUri, projection, null, null, null); + c.moveToPosition(0); - synchronized (this) { - checkCanceled(); - mSaveImageCancelable = image.saveImageContents(source, jpegData, orientation, true, c); - } + synchronized (this) { + checkCanceled(); + mSaveImageCancelable = image.saveImageContents( + mSource, mJpegData, mOrientation, true, c); + } - if (mSaveImageCancelable.get()) { - long t6 = System.currentTimeMillis(); - if (VERBOSE) Log.v(TAG, "saveImageContents took " + (t6-t5)); - if (VERBOSE) Log.v(TAG, "updating new picture with id " + id); - c.updateLong(1, id); - c.commitUpdates(); - c.close(); - long t7 = System.currentTimeMillis(); - if (VERBOSE) Log.v(TAG, "commit updates to save mini thumb took " + (t7-t6)); - } - else { - c.close(); - throw new CanceledException(); + if (mSaveImageCancelable.get()) { + long t6 = System.currentTimeMillis(); + if (VERBOSE) { + Log.v(TAG, "saveImageContents took " + (t6 - t5)); + Log.v(TAG, "updating new picture with id " + id); } - } catch (CanceledException ex) { + c.updateLong(1, id); + c.commitUpdates(); + c.close(); + long t7 = System.currentTimeMillis(); if (VERBOSE) { - Log.v(TAG, "caught CanceledException"); + Log.v(TAG, "commit updates to save mini thumb took " + + (t7 - t6)); } - if (uri != null) { - if (VERBOSE) { - Log.v(TAG, "canceled... cleaning up this uri: " + uri); - } - cr.delete(uri, null, null); + } else { + c.close(); + throw new CanceledException(); + } + } catch (CanceledException ex) { + if (VERBOSE) { + Log.v(TAG, "caught CanceledException"); + } + if (mUri != null) { + if (VERBOSE) { + Log.v(TAG, "canceled... cleaning up this uri: " + mUri); } - acknowledgeCancel(); + mCr.delete(mUri, null, null); } + acknowledgeCancel(); } } - return new AddImageCancelable(); } - static public IImageList makeImageList(Uri uri, Context ctx, int sort) { + public IAddImageCancelable storeImage( + Uri uri, Context ctx, ContentResolver cr, int orientation, + Bitmap source, byte [] jpegData) { + return new AddImageCancelable( + uri, ctx, cr, orientation, source, jpegData); + } + + public static IImageList makeImageList(Uri uri, Context ctx, int sort) { ContentResolver cr = ctx.getContentResolver(); String uriString = (uri != null) ? uri.toString() : ""; - // TODO we need to figure out whether we're viewing + + // TODO: we need to figure out whether we're viewing // DRM images in a better way. Is there a constant // for content://drm somewhere?? IImageList imageList; if (uriString.startsWith("content://drm")) { imageList = ImageManager.instance().allImages( - ctx, - cr, - ImageManager.DataLocation.ALL, - ImageManager.INCLUDE_DRM_IMAGES, - sort); - } else if (!uriString.startsWith(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString()) - && !uriString.startsWith(MediaStore.Images.Media.INTERNAL_CONTENT_URI.toString())) { - imageList = ImageManager.instance().new SingleImageList(cr, uri); + ctx, cr, ImageManager.DataLocation.ALL, + ImageManager.INCLUDE_DRM_IMAGES, sort); + } else if (isSingleImageMode(uriString)) { + imageList = new SingleImageList(cr, uri); } else { String bucketId = uri.getQueryParameter("bucketId"); if (VERBOSE) Log.v(TAG, "bucketId is " + bucketId); imageList = ImageManager.instance().allImages( - ctx, - cr, - ImageManager.DataLocation.ALL, - ImageManager.INCLUDE_IMAGES, - sort, - bucketId); + ctx, cr, ImageManager.DataLocation.ALL, + ImageManager.INCLUDE_IMAGES, sort, bucketId); } return imageList; } - public IImageList emptyImageList() { - return - new IImageList() { - public void checkThumbnails(ImageManager.IImageList.ThumbCheckCallback cb, - int totalThumbnails) { - } + private static boolean isSingleImageMode(String uriString) { + return !uriString.startsWith( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString()) + && !uriString.startsWith( + MediaStore.Images.Media.INTERNAL_CONTENT_URI.toString()); + } - public void commitChanges() { - } + private static class EmptyImageList implements IImageList { + public void checkThumbnails(IImageList.ThumbCheckCallback cb, + int totalThumbnails) { + } - public void deactivate() { - } + public void commitChanges() { + } - public HashMap<String, String> getBucketIds() { - return new HashMap<String,String>(); - } + public void deactivate() { + } - public int getCount() { - return 0; - } + public HashMap<String, String> getBucketIds() { + return new HashMap<String, String>(); + } - public boolean isEmpty() { - return true; - } + public int getCount() { + return 0; + } - public IImage getImageAt(int i) { - return null; - } + public boolean isEmpty() { + return true; + } - public IImage getImageForUri(Uri uri) { - return null; - } + public IImage getImageAt(int i) { + return null; + } - public boolean removeImage(IImage image) { - return false; - } + public IImage getImageForUri(Uri uri) { + return null; + } - public void removeImageAt(int i) { - } + public boolean removeImage(IImage image) { + return false; + } - public void removeOnChangeListener(ImageManager.IImageList.OnChange changeCallback) { - } + public void removeImageAt(int i) { + } - public void setOnChangeListener(ImageManager.IImageList.OnChange changeCallback, - Handler h) { - } + public void removeOnChangeListener(IImageList.OnChange changeCallback) { + } + + public void setOnChangeListener(IImageList.OnChange changeCallback, + Handler h) { + } - }; } - public IImageList allImages(Context ctx, ContentResolver cr, DataLocation location, int inclusion, int sort) { + public IImageList emptyImageList() { + return new EmptyImageList(); + } + + public IImageList allImages(Context ctx, ContentResolver cr, + DataLocation location, int inclusion, int sort) { return allImages(ctx, cr, location, inclusion, sort, null, null); } - public IImageList allImages(Context ctx, ContentResolver cr, DataLocation location, int inclusion, int sort, String bucketId) { + public IImageList allImages(Context ctx, ContentResolver cr, + DataLocation location, int inclusion, int sort, String bucketId) { return allImages(ctx, cr, location, inclusion, sort, bucketId, null); } - public IImageList allImages(Context ctx, ContentResolver cr, DataLocation location, int inclusion, int sort, String bucketId, Uri specificImageUri) { + public IImageList allImages( + Context ctx, ContentResolver cr, DataLocation location, + int inclusion, int sort, String bucketId, Uri specificImageUri) { if (VERBOSE) { - Log.v(TAG, "allImages " + location + " " + ((inclusion&INCLUDE_IMAGES)!=0) + " + v=" + ((inclusion&INCLUDE_VIDEOS)!=0)); + Log.v(TAG, "allImages " + location + " " + + ((inclusion & INCLUDE_IMAGES) != 0) + " + v=" + + ((inclusion & INCLUDE_VIDEOS) != 0)); } if (cr == null) { @@ -4037,43 +485,60 @@ public class ImageManager { ArrayList<IImageList> l = new ArrayList<IImageList>(); if (VERBOSE) { - Log.v(TAG, "initializing ... haveSdCard == " + haveSdCard + "; inclusion is " + String.format("%x", inclusion)); + Log.v(TAG, "initializing ... haveSdCard == " + haveSdCard + + "; inclusion is " + + String.format("%x", inclusion)); } if (specificImageUri != null) { try { - if (specificImageUri.getScheme().equalsIgnoreCase("content")) - l.add(new ImageList(ctx, cr, specificImageUri, sThumbURI, sort, bucketId)); - else + if (specificImageUri.getScheme() + .equalsIgnoreCase("content")) { + l.add(new ImageList(ctx, cr, specificImageUri, + sThumbURI, sort, bucketId)); + } else { l.add(new SingleImageList(cr, specificImageUri)); + } } catch (UnsupportedOperationException ex) { + // ignore exception } } else { if (haveSdCard && location != DataLocation.INTERNAL) { if ((inclusion & INCLUDE_IMAGES) != 0) { try { - l.add(new ImageList(ctx, cr, sStorageURI, sThumbURI, sort, bucketId)); + l.add(new ImageList(ctx, cr, sStorageURI, + sThumbURI, sort, bucketId)); } catch (UnsupportedOperationException ex) { + // ignore exception } } if ((inclusion & INCLUDE_VIDEOS) != 0) { try { - l.add(new VideoList(ctx, cr, sVideoStorageURI, sVideoThumbURI, sort, bucketId)); + l.add(new VideoList(ctx, cr, sVideoStorageURI, + sVideoThumbURI, sort, bucketId)); } catch (UnsupportedOperationException ex) { + // ignore exception } } } - if (location == DataLocation.INTERNAL || location == DataLocation.ALL) { + if (location == DataLocation.INTERNAL + || location == DataLocation.ALL) { if ((inclusion & INCLUDE_IMAGES) != 0) { try { - l.add(new ImageList(ctx, cr, Images.Media.INTERNAL_CONTENT_URI, - Images.Thumbnails.INTERNAL_CONTENT_URI, sort, bucketId)); + l.add(new ImageList(ctx, cr, + Images.Media.INTERNAL_CONTENT_URI, + Images.Thumbnails.INTERNAL_CONTENT_URI, + sort, bucketId)); } catch (UnsupportedOperationException ex) { + // ignore exception } } if ((inclusion & INCLUDE_DRM_IMAGES) != 0) { try { - l.add(new DrmImageList(ctx, cr, DrmStore.Images.CONTENT_URI, sort, bucketId)); + l.add(new DrmImageList(ctx, cr, + DrmStore.Images.CONTENT_URI, + sort, bucketId)); } catch (UnsupportedOperationException ex) { + // ignore exception } } } @@ -4083,19 +548,24 @@ public class ImageManager { return new ImageListUber(imageList, sort); } else { if (haveSdCard && location != DataLocation.INTERNAL) { - return new ImageList(ctx, cr, sStorageURI, sThumbURI, sort, bucketId); + return new ImageList( + ctx, cr, sStorageURI, sThumbURI, sort, bucketId); } else { - return new ImageList(ctx, cr, Images.Media.INTERNAL_CONTENT_URI, - Images.Thumbnails.INTERNAL_CONTENT_URI, sort, bucketId); + return new ImageList(ctx, cr, + Images.Media.INTERNAL_CONTENT_URI, + Images.Thumbnails.INTERNAL_CONTENT_URI, sort, + bucketId); } } } } - // 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. - static private boolean checkFsWritable() { - String directoryName = Environment.getExternalStorageDirectory().toString() + "/DCIM"; + 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()) { @@ -4117,11 +587,11 @@ public class ImageManager { } } - static public boolean hasStorage() { + public static boolean hasStorage() { return hasStorage(true); } - static public boolean hasStorage(boolean requireWriteAccess) { + public static boolean hasStorage(boolean requireWriteAccess) { //TODO: After fix the bug, add "if (VERBOSE)" before logging errors. String state = Environment.getExternalStorageState(); Log.v(TAG, "storage state is " + state); @@ -4134,7 +604,8 @@ public class ImageManager { } else { return true; } - } else if (!requireWriteAccess && Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { + } else if (!requireWriteAccess + && Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { return true; } return false; @@ -4147,7 +618,8 @@ public class ImageManager { if (resolver == null) { return null; } - return resolver.query(uri, projection, selection, selectionArgs, sortOrder); + return resolver.query( + uri, projection, selection, selectionArgs, sortOrder); } catch (UnsupportedOperationException ex) { return null; } @@ -4157,7 +629,8 @@ public class ImageManager { public static boolean isMediaScannerScanning(Context context) { boolean result = false; Cursor cursor = query(context, MediaStore.getMediaScannerUri(), - new String [] { MediaStore.MEDIA_SCANNER_VOLUME }, null, null, null); + new String [] {MediaStore.MEDIA_SCANNER_VOLUME}, + null, null, null); if (cursor != null) { if (cursor.getCount() == 1) { cursor.moveToFirst(); @@ -4166,35 +639,10 @@ public class ImageManager { cursor.close(); } - if (VERBOSE) - Log.v(TAG, ">>>>>>>>>>>>>>>>>>>>>>>>> isMediaScannerScanning returning " + result); - return result; - } - - /** - * Create a video thumbnail for a video. May return null if the video is corrupt. - * @param filePath - * @return - */ - public static Bitmap createVideoThumbnail(String filePath) { - Bitmap bitmap = null; - MediaMetadataRetriever retriever = new MediaMetadataRetriever(); - try { - retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY); - retriever.setDataSource(filePath); - bitmap = retriever.captureFrame(); - } catch(IllegalArgumentException ex) { - // Assume this is a corrupt video file - } catch (RuntimeException ex) { - // Assume this is a corrupt video file. - } finally { - try { - retriever.release(); - } catch (RuntimeException ex) { - // Ignore failures while cleaning up. - } + if (VERBOSE) { + Log.v(TAG, "isMediaScannerScanning returning " + result); } - return bitmap; + return result; } public static String getLastImageThumbPath() { |