summaryrefslogtreecommitdiffstats
path: root/src/com/android/camera/ImageManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/camera/ImageManager.java')
-rwxr-xr-xsrc/com/android/camera/ImageManager.java4158
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() {