diff options
18 files changed, 549 insertions, 813 deletions
diff --git a/src/com/android/camera/ImageGallery.java b/src/com/android/camera/ImageGallery.java index 5610191..c1603b6 100644 --- a/src/com/android/camera/ImageGallery.java +++ b/src/com/android/camera/ImageGallery.java @@ -793,7 +793,7 @@ public class ImageGallery extends Activity implements IImage image = mAllImages.getImageForUri(mCropResultUri); mCropResultUri = null; if (image != null) { - mSelectedIndex = image.getRow(); + mSelectedIndex = mAllImages.getImageIndex(image); } } mGvs.select(mSelectedIndex, false); diff --git a/src/com/android/camera/ImageManager.java b/src/com/android/camera/ImageManager.java index 19113f1..b5ee041 100644..100755 --- a/src/com/android/camera/ImageManager.java +++ b/src/com/android/camera/ImageManager.java @@ -223,7 +223,12 @@ public class ImageManager { long id = ContentUris.parseId(mUri); BaseImageList il = new ImageList(mCr, STORAGE_URI, THUMB_URI, SORT_ASCENDING, null); - Image image = new Image(id, 0, mCr, il, il.getCount(), 0); + + // TODO: Redesign the process of adding new images. We should + // create an <code>IImage</code> in "ImageManager.addImage" + // and pass the image object to here. + Image image = new Image(il, mCr, id, 0, il.contentUri(id), null, + 0, null, 0, null, null, 0); String[] projection = new String[] { ImageColumns._ID, ImageColumns.MINI_THUMB_MAGIC, ImageColumns.DATA}; @@ -324,7 +329,12 @@ public class ImageManager { return false; } - public void removeImageAt(int i) { + public boolean removeImageAt(int i) { + return false; + } + + public int getImageIndex(IImage image) { + throw new UnsupportedOperationException(); } } @@ -360,15 +370,13 @@ public class ImageManager { } if ((inclusion & INCLUDE_VIDEOS) != 0) { try { - l.add(new VideoList(cr, VIDEO_STORAGE_URI, - VIDEO_THUMBNAIL_URI, sort, bucketId)); + l.add(new VideoList(cr, VIDEO_STORAGE_URI, 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(cr, diff --git a/src/com/android/camera/TimeCounter.java b/src/com/android/camera/TimeCounter.java index 2e1ae7f..d21df0f 100644 --- a/src/com/android/camera/TimeCounter.java +++ b/src/com/android/camera/TimeCounter.java @@ -30,9 +30,9 @@ import android.util.Log; // between begin() and end() every 128 times. public class TimeCounter { private static final String TAG = "TimeCounter"; - private String mName; + private final String mName; private int mSamples; - private int mPeriod; + private final int mPeriod; // To avoid overflow, these values are in microseconds. private long mSum; @@ -81,4 +81,4 @@ public class TimeCounter { + ", Max = " + mMax + ", Min = " + mMin); } -}; +} diff --git a/src/com/android/camera/Util.java b/src/com/android/camera/Util.java index 028725b..d19d698 100644 --- a/src/com/android/camera/Util.java +++ b/src/com/android/camera/Util.java @@ -276,7 +276,7 @@ public class Util { return bitmap; } - public static int indexOf(String [] array, String s) { + public static <T> int indexOf(T [] array, T s) { for (int i = 0; i < array.length; i++) { if (array[i].equals(s)) { return i; diff --git a/src/com/android/camera/ViewImage.java b/src/com/android/camera/ViewImage.java index 0cdcdbc..9ce5e5c 100644 --- a/src/com/android/camera/ViewImage.java +++ b/src/com/android/camera/ViewImage.java @@ -876,7 +876,7 @@ public class ViewImage extends Activity implements View.OnClickListener { IImage image = mAllImages.getImageForUri(uri); if (image != null) { - mCurrentPosition = image.getRow(); + mCurrentPosition = mAllImages.getImageIndex(image); mLastSlideShowImage = mCurrentPosition; } } diff --git a/src/com/android/camera/gallery/BaseImage.java b/src/com/android/camera/gallery/BaseImage.java index 1caae09..815c0ef 100644 --- a/src/com/android/camera/gallery/BaseImage.java +++ b/src/com/android/camera/gallery/BaseImage.java @@ -20,19 +20,18 @@ import com.android.camera.BitmapManager; import com.android.camera.Util; import android.content.ContentResolver; -import android.database.Cursor; +import android.content.ContentValues; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.ParcelFileDescriptor; -import android.provider.MediaStore.Images; +import android.provider.MediaStore.Images.ImageColumns; import android.util.Log; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.HashMap; /** * Represents a particular image and provides access to the underlying bitmap @@ -47,21 +46,38 @@ public abstract class BaseImage implements IImage { new byte[MiniThumbFile.BYTES_PER_MINTHUMB]; protected ContentResolver mContentResolver; - protected long mId, mMiniThumbMagic; + + + // Database field + protected Uri mUri; + protected long mId; + protected String mDataPath; + protected long mMiniThumbMagic; + protected final int mIndex; + protected String mMimeType; + private final long mDateTaken; + private String mTitle; + private final String mDisplayName; + protected BaseImageList mContainer; - protected HashMap<String, String> mExifData; - protected int mCursorRow; private int mWidth = UNKNOWN_LENGTH; private int mHeight = UNKNOWN_LENGTH; - protected BaseImage(long id, long miniThumbMagic, ContentResolver cr, - BaseImageList container, int cursorRow) { + protected BaseImage(BaseImageList container, ContentResolver cr, + long id, int index, Uri uri, String dataPath, long miniThumbMagic, + String mimeType, long dateTaken, String title, String displayName) { + mContainer = container; mContentResolver = cr; - mId = id; - mMiniThumbMagic = miniThumbMagic; - mContainer = container; - mCursorRow = cursorRow; + mId = id; + mIndex = index; + mUri = uri; + mDataPath = dataPath; + mMiniThumbMagic = miniThumbMagic; + mMimeType = mimeType; + mDateTaken = dateTaken; + mTitle = title; + mDisplayName = displayName; } protected abstract Bitmap.CompressFormat compressionType(); @@ -70,12 +86,12 @@ public abstract class BaseImage implements IImage { private ThreadSafeOutputStream mOutputStream = null; private final Bitmap mBitmap; - private final Uri mUri; + private final Uri mDestinationUri; private final byte[] mJpegData; public CompressImageToFile(Bitmap bitmap, byte[] jpegData, Uri uri) { mBitmap = bitmap; - mUri = uri; + mDestinationUri = uri; mJpegData = jpegData; } @@ -93,7 +109,8 @@ public abstract class BaseImage implements IImage { @Override public Boolean execute() { try { - OutputStream delegate = mContentResolver.openOutputStream(mUri); + OutputStream delegate = + mContentResolver.openOutputStream(mDestinationUri); synchronized (this) { mOutputStream = new ThreadSafeOutputStream(delegate); } @@ -126,15 +143,19 @@ public abstract class BaseImage implements IImage { return new CompressImageToFile(bitmap, jpegData, uri); } + public String getDataPath() { + return mDataPath; + } + @Override public boolean equals(Object other) { if (other == null || !(other instanceof Image)) return false; - return fullSizeImageUri().equals(((Image) other).fullSizeImageUri()); + return mUri.equals(((Image) other).mUri); } @Override public int hashCode() { - return fullSizeImageUri().toString().hashCode(); + return mUri.hashCode(); } public Bitmap fullSizeBitmap(int targetWidthHeight) { @@ -177,8 +198,7 @@ public abstract class BaseImage implements IImage { @Override protected Bitmap execute() { try { - Bitmap b = Util.makeBitmap( - mTargetWidthHeight, fullSizeImageUri(), + Bitmap b = Util.makeBitmap(mTargetWidthHeight, mUri, mContentResolver, mPFD, mOptions); if (b != null) { b = Util.rotate(b, getDegreesRotated()); @@ -192,12 +212,11 @@ public abstract class BaseImage implements IImage { } } - public Cancelable<Bitmap> fullSizeBitmapCancelable( int targetWidthHeight) { try { ParcelFileDescriptor pfdInput = mContentResolver - .openFileDescriptor(fullSizeImageUri(), "r"); + .openFileDescriptor(mUri, "r"); return new LoadBitmapCancelable(pfdInput, targetWidthHeight); } catch (FileNotFoundException ex) { return null; @@ -208,8 +227,7 @@ public abstract class BaseImage implements IImage { public InputStream fullSizeImageData() { try { - InputStream input = mContentResolver.openInputStream( - fullSizeImageUri()); + InputStream input = mContentResolver.openInputStream(mUri); return input; } catch (IOException ex) { return null; @@ -221,24 +239,15 @@ public abstract class BaseImage implements IImage { } public Uri fullSizeImageUri() { - return mContainer.contentUri(mId); + return mUri; } 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()); - } + return mDateTaken; } protected int getDegreesRotated() { @@ -246,74 +255,21 @@ public abstract class BaseImage implements IImage { } public String getMimeType() { - if (mContainer.indexMimeType() < 0) { - Cursor c = mContentResolver.query(fullSizeImageUri(), - new String[] { "_id", Images.Media.MIME_TYPE }, - null, null, null); - try { - return c.moveToFirst() ? c.getString(1) : ""; - } finally { - c.close(); - } - } else { - String mimeType = null; - Cursor c = getCursor(); - synchronized (c) { - if (c.moveToPosition(getRow())) { - mimeType = c.getString(mContainer.indexMimeType()); - } - } - return mimeType; - } + return mMimeType; } 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); + return mTitle; } public String getDisplayName() { - if (mContainer.indexDisplayName() < 0) { - Cursor c = mContentResolver.query(fullSizeImageUri(), - new String[] { "_id", Images.Media.DISPLAY_NAME }, - null, null, null); - try { - if (c.moveToFirst()) return c.getString(1); - } finally { - 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 int getRow() { - return mCursorRow; + return mDisplayName; } private void setupDimension() { ParcelFileDescriptor input = null; try { - input = mContentResolver - .openFileDescriptor(fullSizeImageUri(), "r"); + input = mContentResolver.openFileDescriptor(mUri, "r"); BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapManager.instance().decodeFileDescriptor( @@ -343,8 +299,8 @@ public abstract class BaseImage implements IImage { long id = mId; long dbMagic = mMiniThumbMagic; if (dbMagic == 0 || dbMagic == id) { - dbMagic = ((BaseImageList) getContainer()) - .checkThumbnail(this, getRow(), null); + dbMagic = ((BaseImageList) + getContainer()).checkThumbnail(this, null); } synchronized (sMiniThumbData) { @@ -355,8 +311,7 @@ public abstract class BaseImage implements IImage { byte[][] createdThumbData = new byte[1][]; try { dbMagic = ((BaseImageList) getContainer()) - .checkThumbnail(this, getRow(), - createdThumbData); + .checkThumbnail(this, createdThumbData); } catch (IOException ex) { // Typically IOException because the sd card is full. // But createdThumbData may have been filled in, so @@ -388,8 +343,7 @@ public abstract class BaseImage implements IImage { } } - public void onRemove() { - mContainer.mCache.remove(mId); + protected void onRemove() { } protected void saveMiniThumb(Bitmap source) throws IOException { @@ -397,25 +351,21 @@ public abstract class BaseImage implements IImage { } public void setTitle(String name) { - Cursor c = getCursor(); - synchronized (c) { - if (c.moveToPosition(getRow())) { - c.updateString(mContainer.indexTitle(), name); - c.commitUpdates(); - } - } + if (mTitle.equals(name)) return; + mTitle = name; + ContentValues values = new ContentValues(); + values.put(ImageColumns.TITLE, name); + mContentResolver.update(mUri, values, null, null); } 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; + return mUri.buildUpon().appendQueryParameter("thumb", "1").build(); } @Override public String toString() { - return fullSizeImageUri().toString(); + return mUri.toString(); } } diff --git a/src/com/android/camera/gallery/BaseImageList.java b/src/com/android/camera/gallery/BaseImageList.java index ee56f80..1d29231 100644 --- a/src/com/android/camera/gallery/BaseImageList.java +++ b/src/com/android/camera/gallery/BaseImageList.java @@ -17,7 +17,6 @@ package com.android.camera.gallery; import com.android.camera.ExifInterface; -import com.android.camera.ImageManager; import com.android.camera.Util; import android.content.ContentResolver; @@ -27,31 +26,35 @@ import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; +import android.os.SystemClock; import android.provider.BaseColumns; -import android.provider.MediaStore.Images; +import android.provider.MediaStore.Images.ImageColumns; import android.provider.MediaStore.Images.Thumbnails; import android.util.Log; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; -import java.util.HashMap; +import java.util.Random; /** * A collection of <code>BaseImage</code>s. */ public abstract class BaseImageList implements IImageList { private static final String TAG = "BaseImageList"; + private static final int CACHE_CAPACITY = 512; + private final LruCache<Integer, BaseImage> mCache = + new LruCache<Integer, BaseImage>(CACHE_CAPACITY); protected ContentResolver mContentResolver; protected int mSort; + protected Uri mBaseUri; protected Cursor mCursor; - protected boolean mCursorDeactivated; protected String mBucketId; - protected HashMap<Long, IImage> mCache = new HashMap<Long, IImage>(); protected MiniThumbFile mMiniThumbFile; protected Uri mThumbUri; + protected boolean mCursorDeactivated = false; public BaseImageList(ContentResolver cr, Uri uri, int sort, String bucketId) { @@ -60,16 +63,15 @@ public abstract class BaseImageList implements IImageList { mBucketId = bucketId; mContentResolver = cr; mMiniThumbFile = new MiniThumbFile(uri); + mCursor = createCursor(); } /** * Store a given thumbnail in the database. */ - protected Bitmap storeThumbnail(Bitmap thumb, long imageId) { + protected Bitmap storeThumbnail(Bitmap thumb, Uri uri) { if (thumb == null) return null; try { - Uri uri = getThumbnailUri( - imageId, thumb.getWidth(), thumb.getHeight()); if (uri == null) { return thumb; } @@ -111,39 +113,32 @@ public abstract class BaseImageList implements IImageList { }; 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 = mContentResolver.query(mThumbUri, THUMB_PROJECTION, Thumbnails.IMAGE_ID + "=?", new String[]{String.valueOf(imageId)}, null); try { - if (c.moveToFirst()) { - // If, for some reason, 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(0)); + if (c.moveToNext()) { + return ContentUris.withAppendedId(mThumbUri, c.getLong(0)); } } finally { 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; + ContentValues values = new ContentValues(4); + values.put(Thumbnails.KIND, Thumbnails.MINI_KIND); + values.put(Thumbnails.IMAGE_ID, imageId); + values.put(Thumbnails.HEIGHT, height); + values.put(Thumbnails.WIDTH, width); + return mContentResolver.insert(mThumbUri, values); } - private static final java.util.Random sRandom = - new java.util.Random(System.currentTimeMillis()); + private static final Random sRandom = + new Random(System.currentTimeMillis()); // 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 @@ -190,12 +185,11 @@ public abstract class BaseImageList implements IImageList { // 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); + private Bitmap createThumbnailFromUri(Uri uri) { Bitmap bitmap = Util.makeBitmap(IImage.THUMBNAIL_TARGET_SIZE, uri, mContentResolver); if (bitmap != null) { - storeThumbnail(bitmap, id); + storeThumbnail(bitmap, uri); } else { bitmap = Util.makeBitmap(IImage.MINI_THUMB_TARGET_SIZE, uri, mContentResolver); @@ -204,14 +198,12 @@ public abstract class BaseImageList implements IImageList { } public void checkThumbnail(int index) throws IOException { - checkThumbnail(null, index, null); + checkThumbnail((BaseImage) getImageAt(index), 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 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 @@ -221,26 +213,12 @@ public abstract class BaseImageList implements IImageList { * thumbnail even if the sdcard is full. * @throws IOException */ - public long checkThumbnail(BaseImage existingImage, int i, + public long checkThumbnail(BaseImage existingImage, byte[][] createdThumbnailData) throws IOException { long magic, id; - Cursor c = getCursor(); - 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(indexMiniThumbMagic()); - id = c.getLong(indexId()); - } - } else { - // if we have an Image object then ask them for the magic/id - magic = existingImage.mMiniThumbMagic; - id = existingImage.fullSizeImageId(); - } + magic = existingImage.mMiniThumbMagic; + id = existingImage.fullSizeImageId(); if (magic != 0) { long fileMagic = mMiniThumbFile.getMagic(id); @@ -253,32 +231,23 @@ public abstract class BaseImageList implements IImageList { // 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()); - } - } + String filePath = existingImage.getDataPath(); + if (filePath != null) { - String mimeType = c.getString(indexMimeType()); + String mimeType = existingImage.getMimeType(); boolean isVideo = Util.isVideoMimeType(mimeType); if (isVideo) { bitmap = Util.createVideoThumbnail(filePath); } else { bitmap = createThumbnailFromEXIF(filePath, id); if (bitmap == null) { - bitmap = createThumbnailFromUri(c, id); + bitmap = createThumbnailFromUri( + ContentUris.withAppendedId(mBaseUri, id)); } } - synchronized (c) { - int degrees = 0; - if (c.moveToPosition(i)) { - int column = indexOrientation(); - if (column >= 0) degrees = c.getInt(column); - } - if (degrees != 0) { - bitmap = Util.rotate(bitmap, degrees); - } + int degrees = existingImage.getDegreesRotated(); + if (degrees != 0) { + bitmap = Util.rotate(bitmap, degrees); } } @@ -286,6 +255,7 @@ public abstract class BaseImageList implements IImageList { do { magic = sRandom.nextLong(); } while (magic == 0); + if (bitmap != null) { byte [] data = Util.miniThumbData(bitmap); if (createdThumbnailData != null) { @@ -294,21 +264,18 @@ public abstract class BaseImageList implements IImageList { saveMiniThumbToFile(data, id, magic); } - synchronized (c) { - c.moveToPosition(i); - c.updateLong(indexMiniThumbMagic(), magic); - c.commitUpdates(); - c.requery(); - c.moveToPosition(i); - - if (existingImage != null) { - existingImage.mMiniThumbMagic = magic; - } - return magic; - } + ContentValues values = new ContentValues(); + values.put(ImageColumns.MINI_THUMB_MAGIC, magic); + mContentResolver.update( + existingImage.fullSizeImageUri(), values, null, null); + existingImage.mMiniThumbMagic = magic; + return magic; } - protected Uri contentUri(long id) { + // TODO: Change public to protected + public Uri contentUri(long id) { + + // TODO: avoid using exception for most cases try { // does our uri already have an id (single image query)? // if so just return it @@ -322,9 +289,8 @@ public abstract class BaseImageList implements IImageList { } public void deactivate() { - mCursorDeactivated = true; try { - mCursor.deactivate(); + invalidateCursor(); } catch (IllegalStateException e) { // IllegalStateException may be thrown if the cursor is stale. Log.e(TAG, "Caught exception while deactivating cursor.", e); @@ -333,13 +299,9 @@ public abstract class BaseImageList implements IImageList { } public int getCount() { - Cursor c = getCursor(); - synchronized (c) { - try { - return c.getCount(); - } catch (RuntimeException ex) { - return 0; - } + Cursor cursor = getCursor(); + synchronized (cursor) { + return cursor.getCount(); } } @@ -347,57 +309,28 @@ public abstract class BaseImageList implements IImageList { return getCount() == 0; } - protected Cursor getCursor() { + private Cursor getCursor() { synchronized (mCursor) { if (mCursorDeactivated) { - activateCursor(); + mCursor.requery(); + mCursorDeactivated = false; } return mCursor; } } - protected void activateCursor() { - requery(); - } - public IImage getImageAt(int i) { - Cursor c = getCursor(); - synchronized (c) { - boolean moved; - try { - moved = c.moveToPosition(i); - } catch (RuntimeException ex) { - return null; - } - if (moved) { - try { - long id = c.getLong(indexId()); - IImage img = mCache.get(id); - if (img == null) { - long miniThumbMagic = 0; - int rotation = 0; - if (indexMiniThumbMagic() != -1) { - miniThumbMagic = c.getLong(indexMiniThumbMagic()); - } - if (indexOrientation() != -1) { - rotation = c.getInt(indexOrientation()); - } - img = make(id, miniThumbMagic, mContentResolver, this, - i, rotation); - mCache.put(id, img); - } - return img; - } catch (RuntimeException ex) { - Log.e(TAG, "got this exception trying to create image: " - + ex); - return null; - } - } else { - Log.e(TAG, "unable to moveTo to " + i + "; count is " - + c.getCount()); - return null; + BaseImage result = mCache.get(i); + if (result == null) { + Cursor cursor = getCursor(); + synchronized (cursor) { + result = cursor.moveToPosition(i) + ? loadImageFromCursor(cursor) + : null; + mCache.put(i, result); } } + return result; } byte [] getMiniThumbFromFile(long id, byte [] data, long magicCheck) { @@ -414,81 +347,36 @@ public abstract class BaseImageList implements IImageList { mMiniThumbFile.saveMiniThumbToFile(data, id, magic); } - protected abstract int indexId(); - - protected abstract int indexData(); - - protected abstract int indexMimeType(); - - protected abstract int indexDateTaken(); - - protected abstract int indexMiniThumbMagic(); - - protected abstract int indexOrientation(); + public boolean removeImage(IImage image) { + // TODO: need to delete the thumbnails as well + if (mContentResolver.delete(image.fullSizeImageUri(), null, null) > 0) { + ((BaseImage) image).onRemove(); + invalidateCursor(); + invalidateCache(); + return true; + } else { + return false; + } + } - protected abstract int indexTitle(); + public boolean removeImageAt(int i) { + // TODO: need to delete the thumbnails as well + return removeImage(getImageAt(i)); + } - protected abstract int indexDisplayName(); + abstract protected Cursor createCursor(); - protected IImage make(long id, long miniThumbMagic, ContentResolver cr, - IImageList list, int index, int rotation) { - return null; - } + abstract protected BaseImage loadImageFromCursor(Cursor cursor); - 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 (RuntimeException 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; - } + abstract protected long getImageId(Cursor cursor); - 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 - */ - IImage image = getImageAt(i); - boolean moved; - try { - moved = c.moveToPosition(i); - } catch (RuntimeException ex) { - Log.e(TAG, "removeImageAt " + i + " get " + ex); - return; - } - if (moved) { - Uri u = image.fullSizeImageUri(); - mContentResolver.delete(u, null, null); - requery(); - image.onRemove(); - } - } + protected void invalidateCursor() { + mCursor.deactivate(); + mCursorDeactivated = true; } - protected void requery() { + protected void invalidateCache() { mCache.clear(); - mCursor.requery(); - mCursorDeactivated = false; } public IImage getImageForUri(Uri uri) { @@ -497,20 +385,26 @@ public abstract class BaseImageList implements IImageList { try { matchId = ContentUris.parseId(uri); } catch (NumberFormatException ex) { + Log.i(TAG, "fail to get id in: " + uri, ex); return null; } - - // Go through the data and find the entry with the matching id. - Cursor c = getCursor(); - synchronized (c) { - int index = indexId(); - if (!c.moveToFirst()) return null; - do { - if (matchId == c.getLong(index)) { - return getImageAt(c.getPosition()); + // TODO: design a better method to get URI of specified ID + long startTimestamp = SystemClock.elapsedRealtime(); + Cursor cursor = getCursor(); + synchronized (cursor) { + cursor.moveToPosition(-1); // before first + for (int i = 0; cursor.moveToNext(); ++i) { + if (getImageId(cursor) == matchId) { + Log.v(TAG, "find object at " + i + ", it takes" + + (SystemClock.elapsedRealtime() - startTimestamp)); + return loadImageFromCursor(cursor); } - } while (c.moveToNext()); + } return null; } } + + public int getImageIndex(IImage image) { + return ((BaseImage) image).mIndex; + } } diff --git a/src/com/android/camera/gallery/DrmImageList.java b/src/com/android/camera/gallery/DrmImageList.java index 383211a..2a55343 100644 --- a/src/com/android/camera/gallery/DrmImageList.java +++ b/src/com/android/camera/gallery/DrmImageList.java @@ -29,12 +29,17 @@ import android.provider.DrmStore; */ public class DrmImageList extends ImageList implements IImageList { + // TODO: get other field from database too ? private static final String[] DRM_IMAGE_PROJECTION = new String[] { - DrmStore.Audio._ID, - DrmStore.Audio.DATA, - DrmStore.Audio.MIME_TYPE, + DrmStore.Images._ID, + DrmStore.Images.DATA, + DrmStore.Images.MIME_TYPE, }; + private static final int INDEX_ID = 0; + private static final int INDEX_DATA_PATH = 1; + private static final int INDEX_MIME_TYPE = 2; + public DrmImageList(ContentResolver cr, Uri imageUri, int sort, String bucketId) { super(cr, imageUri, null, sort, bucketId); @@ -52,9 +57,12 @@ public class DrmImageList extends ImageList implements IImageList { private static class DrmImage extends Image { - protected DrmImage(long id, ContentResolver cr, - BaseImageList container, int cursorRow) { - super(id, 0, cr, container, cursorRow, 0); + protected DrmImage(BaseImageList container, ContentResolver cr, + long id, int index, Uri uri, String dataPath, long miniThumbMagic, + String mimeType, long dateTaken, String title, + String displayName, int rotation) { + super(container, cr, id, index, uri, dataPath, miniThumbMagic, + mimeType, dateTaken, title, displayName, rotation); } @Override @@ -84,54 +92,18 @@ public class DrmImageList extends ImageList implements IImageList { } @Override - protected IImage make(long id, long miniThumbMagic, ContentResolver cr, - IImageList list, int index, int rotation) { - return new DrmImage(id, mContentResolver, this, index); - } - - - @Override - protected int indexId() { - return -1; - } - - @Override - protected int indexData() { - return -1; - } - - @Override - protected int indexMimeType() { - return -1; - } - - @Override - protected int indexDateTaken() { - return -1; - } - - @Override - protected int indexMiniThumbMagic() { - return -1; - } - - @Override - protected int indexOrientation() { - return -1; - } - - @Override - protected int indexTitle() { - return -1; - } - - @Override - protected int indexDisplayName() { - return -1; + protected BaseImage loadImageFromCursor(Cursor cursor) { + long id = cursor.getLong(INDEX_ID); + String dataPath = cursor.getString(INDEX_DATA_PATH); + String mimeType = cursor.getString(INDEX_MIME_TYPE); + return new DrmImage(this, mContentResolver, id, cursor.getPosition(), + contentUri(id), dataPath, 0, mimeType, 0, "DrmImage-" + id, + "DrmImage-" + id, 0); } // TODO: Review this probably should be based on DATE_TAKEN same as images - private String sortOrder() { + @Override + protected String sortOrder() { String ascending = mSort == ImageManager.SORT_ASCENDING ? " ASC" : " DESC"; return DrmStore.Images.TITLE + ascending + "," + DrmStore.Images._ID; diff --git a/src/com/android/camera/gallery/IImage.java b/src/com/android/camera/gallery/IImage.java index 9c47d13..17030f0 100644 --- a/src/com/android/camera/gallery/IImage.java +++ b/src/com/android/camera/gallery/IImage.java @@ -52,9 +52,13 @@ public interface IImage { // Get metadata of the image public abstract long getDateTaken(); + public abstract String getMimeType(); + public abstract int getWidth(); + public abstract int getHeight(); + public abstract String getDisplayName(); // Get property of the image @@ -68,13 +72,7 @@ public interface IImage { // Get the bitmap of the mini thumbnail. public abstract Bitmap miniThumbBitmap(); - // Get the row number for this image in the database table. - public abstract int getRow(); - // Rotate the image public abstract boolean rotateImageBy(int degrees); - // This is called if the image is removed. - public abstract void onRemove(); - } diff --git a/src/com/android/camera/gallery/IImageList.java b/src/com/android/camera/gallery/IImageList.java index 26fb017..b334e67 100644 --- a/src/com/android/camera/gallery/IImageList.java +++ b/src/com/android/camera/gallery/IImageList.java @@ -89,7 +89,9 @@ public interface IImageList { * Removes the image at the ith position. * @param i the position */ - public abstract void removeImageAt(int i); + public abstract boolean removeImageAt(int i); + + public int getImageIndex(IImage image); /** * Generate thumbnail for the image (if it has not been generated.) diff --git a/src/com/android/camera/gallery/Image.java b/src/com/android/camera/gallery/Image.java index 8c208e2..5c09aba 100644 --- a/src/com/android/camera/gallery/Image.java +++ b/src/com/android/camera/gallery/Image.java @@ -22,17 +22,20 @@ import com.android.camera.Util; import android.content.ContentResolver; import android.content.ContentUris; +import android.content.ContentValues; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.provider.BaseColumns; +import android.provider.MediaStore.Images.ImageColumns; import android.provider.MediaStore.Images.Thumbnails; import android.util.Log; import java.io.FileNotFoundException; import java.io.IOException; +import java.util.HashMap; import java.util.concurrent.ExecutionException; /** @@ -41,45 +44,34 @@ import java.util.concurrent.ExecutionException; public class Image extends BaseImage implements IImage { private static final String TAG = "BaseImage"; - private int mRotation; + private HashMap<String, String> mExifData; + private ExifInterface mExif; + private int mRotation; - public Image(long id, long miniThumbMagic, ContentResolver cr, - BaseImageList container, int cursorRow, int rotation) { - super(id, miniThumbMagic, cr, container, cursorRow); + public Image(BaseImageList container, ContentResolver cr, + long id, int index, Uri uri, String dataPath, long miniThumbMagic, + String mimeType, long dateTaken, String title, String displayName, + int rotation) { + super(container, cr, id, index, uri, dataPath, miniThumbMagic, + mimeType, dateTaken, title, displayName); 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; - } - @Override protected int getDegreesRotated() { return mRotation; } protected void setDegreesRotated(int degrees) { - Cursor c = getCursor(); + if (mRotation == degrees) return; mRotation = degrees; - synchronized (c) { - if (c.moveToPosition(getRow())) { - int column = ((ImageList) getContainer()).indexOrientation(); - if (column >= 0) { - c.updateInt(column, degrees); - c.commitUpdates(); - } - } - } + ContentValues values = new ContentValues(); + values.put(ImageColumns.ORIENTATION, mRotation); + mContentResolver.update(mUri, values, null, null); + + //TODO: Consider invalidate the cursor in container + // ((BaseImageList) getContainer()).invalidateCursor(); } @Override @@ -94,7 +86,7 @@ public class Image extends BaseImage implements IImage { } /** - * Does not replace the tag if already there. Otherwise, adds to the exif + * Does not replace the tag if already there. Otherwise, adds to the EXIF * tags. * * @param tag @@ -111,8 +103,7 @@ public class Image extends BaseImage implements IImage { } /** - * Return the value of the Exif tag as an int. Returns 0 on any type of - * error. + * Returns the value of the EXIF tag as an integer. * * @param tag */ @@ -130,7 +121,7 @@ public class Image extends BaseImage implements IImage { } /** - * Return the value of the Exif tag as a String. It's caller's + * Return the value of the EXIF tag as a String. It's caller's * responsibility to check nullity. */ public String getExifTag(String tag) { @@ -219,7 +210,7 @@ public class Image extends BaseImage implements IImage { } mContainer.storeThumbnail( - thumbnail, Image.this.fullSizeImageId()); + thumbnail, Image.this.fullSizeImageUri()); if (isCanceling()) return null; try { @@ -240,12 +231,7 @@ public class Image extends BaseImage implements IImage { } private void loadExifData() { - Cursor c = getCursor(); - String filePath; - synchronized (c) { - filePath = c.getString(mContainer.indexData()); - } - mExif = new ExifInterface(filePath); + mExif = new ExifInterface(mDataPath); if (mExifData == null) { mExifData = mExif.getAttributes(); } @@ -301,7 +287,7 @@ public class Image extends BaseImage implements IImage { // fresh thumbs mMiniThumbMagic = 0; try { - mContainer.checkThumbnail(this, this.getRow(), null); + mContainer.checkThumbnail(this, null); } catch (IOException e) { // Ignore inability to store mini thumbnail. } @@ -335,7 +321,7 @@ public class Image extends BaseImage implements IImage { if (bitmap == null) { bitmap = fullSizeBitmap(THUMBNAIL_TARGET_SIZE, false); // No thumbnail found... storing the new one. - bitmap = mContainer.storeThumbnail(bitmap, fullSizeImageId()); + bitmap = mContainer.storeThumbnail(bitmap, fullSizeImageUri()); } if (bitmap != null) { diff --git a/src/com/android/camera/gallery/ImageList.java b/src/com/android/camera/gallery/ImageList.java index c39fe4d..2c96265 100644 --- a/src/com/android/camera/gallery/ImageList.java +++ b/src/com/android/camera/gallery/ImageList.java @@ -17,16 +17,12 @@ package com.android.camera.gallery; import com.android.camera.ImageManager; -import com.android.camera.Util; import android.content.ContentResolver; import android.database.Cursor; import android.net.Uri; -import android.provider.BaseColumns; import android.provider.MediaStore.Images; -import android.provider.MediaStore.Images.ImageColumns; -import android.provider.MediaStore.MediaColumns; -import android.util.Log; +import android.provider.MediaStore.Images.Media; import java.util.HashMap; @@ -36,6 +32,7 @@ import java.util.HashMap; */ public class ImageList extends BaseImageList implements IImageList { + @SuppressWarnings("unused") private static final String TAG = "ImageList"; private static final String[] ACCEPTABLE_IMAGE_TYPES = @@ -44,129 +41,103 @@ public class ImageList extends BaseImageList implements IImageList { public HashMap<String, String> getBucketIds() { Uri uri = mBaseUri.buildUpon() .appendQueryParameter("distinct", "true").build(); - Cursor c = Images.Media.query( + Cursor cursor = Media.query( mContentResolver, uri, 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()); + Media.BUCKET_DISPLAY_NAME, + Media.BUCKET_ID}, + whereClause(), whereClauseArgs(), null); + try { + HashMap<String, String> hash = new HashMap<String, String>(); + while (cursor.moveToNext()) { + hash.put(cursor.getString(1), cursor.getString(0)); + } + return hash; + } finally { + cursor.close(); } - return hash; } + /** * ImageList constructor. - * @param cr ContentResolver */ public ImageList(ContentResolver cr, Uri imageUri, Uri thumbUri, int sort, String bucketId) { super(cr, imageUri, sort, bucketId); mThumbUri = thumbUri; - - mCursor = createCursor(); - if (mCursor == null) { - Log.e(TAG, "unable to create image cursor for " + mBaseUri); - throw new UnsupportedOperationException(); - } } private static final String WHERE_CLAUSE = - "(" + Images.Media.MIME_TYPE + " in (?, ?, ?))"; + "(" + Media.MIME_TYPE + " in (?, ?, ?))"; + private static final String WHERE_CLAUSE_WITH_BUCKET_ID = + WHERE_CLAUSE + " AND " + Media.BUCKET_ID + " = ?"; + protected String whereClause() { - if (mBucketId != null) { - return WHERE_CLAUSE + " and " + Images.Media.BUCKET_ID + " = '" - + mBucketId + "'"; - } else { - return WHERE_CLAUSE; - } + return mBucketId == null ? WHERE_CLAUSE : WHERE_CLAUSE_WITH_BUCKET_ID; } protected String[] whereClauseArgs() { + // TODO: Since mBucketId won't change, we should keep the array. + if (mBucketId != null) { + int count = ACCEPTABLE_IMAGE_TYPES.length; + String[] result = new String[count + 1]; + System.arraycopy(ACCEPTABLE_IMAGE_TYPES, 0, result, 0, count); + result[count] = mBucketId; + return result; + } return ACCEPTABLE_IMAGE_TYPES; } + @Override protected Cursor createCursor() { - Cursor c = Images.Media.query( + Cursor c = Media.query( mContentResolver, mBaseUri, IMAGE_PROJECTION, whereClause(), whereClauseArgs(), sortOrder()); return c; } static final String[] IMAGE_PROJECTION = new String[] { - BaseColumns._ID, - MediaColumns.DATA, - ImageColumns.DATE_TAKEN, - ImageColumns.MINI_THUMB_MAGIC, - ImageColumns.ORIENTATION, - ImageColumns.MIME_TYPE}; - - private static final int INDEX_ID - = Util.indexOf(IMAGE_PROJECTION, BaseColumns._ID); - private static final int INDEX_DATA = - Util.indexOf(IMAGE_PROJECTION, MediaColumns.DATA); - private static final int INDEX_MIME_TYPE = - Util.indexOf(IMAGE_PROJECTION, MediaColumns.MIME_TYPE); - private static final int INDEX_DATE_TAKEN = - Util.indexOf(IMAGE_PROJECTION, ImageColumns.DATE_TAKEN); - private static final int INDEX_MINI_THUMB_MAGIC = - Util.indexOf(IMAGE_PROJECTION, ImageColumns.MINI_THUMB_MAGIC); - private static final int INDEX_ORIENTATION = - Util.indexOf(IMAGE_PROJECTION, ImageColumns.ORIENTATION); - - @Override - protected int indexId() { - return INDEX_ID; - } - - @Override - protected int indexData() { - return INDEX_DATA; - } + Media._ID, + Media.DATA, + Media.DATE_TAKEN, + Media.MINI_THUMB_MAGIC, + Media.ORIENTATION, + Media.TITLE, + Media.MIME_TYPE}; + + private static final int INDEX_ID = 0; + private static final int INDEX_DATA_PATH = 1; + private static final int INDEX_DATE_TAKEN = 2; + private static final int INDEX_MINI_THUMB_MAGIC = 3; + private static final int INDEX_ORIENTATION = 4; + private static final int INDEX_TITLE = 5; + private static final int INDEX_MIME_TYPE = 6; @Override - protected int indexMimeType() { - return INDEX_MIME_TYPE; + protected long getImageId(Cursor cursor) { + return cursor.getLong(INDEX_ID); } @Override - protected int indexDateTaken() { - return INDEX_DATE_TAKEN; - } - - @Override - protected int indexMiniThumbMagic() { - return INDEX_MINI_THUMB_MAGIC; - } - - @Override - protected int indexOrientation() { - return INDEX_ORIENTATION; - } - - @Override - protected int indexTitle() { - return -1; - } - - @Override - protected int indexDisplayName() { - return -1; - } - - @Override - protected IImage make(long id, long miniThumbMagic, ContentResolver cr, - IImageList list, int index, int rotation) { - return new Image(id, miniThumbMagic, mContentResolver, this, index, - rotation); + protected BaseImage loadImageFromCursor(Cursor cursor) { + long id = cursor.getLong(INDEX_ID); + String dataPath = cursor.getString(INDEX_DATA_PATH); + long dateTaken = cursor.getLong(INDEX_DATE_TAKEN); + long miniThumbMagic = cursor.getLong(INDEX_MINI_THUMB_MAGIC); + int orientation = cursor.getInt(INDEX_ORIENTATION); + String title = cursor.getString(INDEX_TITLE); + String mimeType = cursor.getString(INDEX_MIME_TYPE); + if (title == null || title.length() == 0) { + title = dataPath; + } + String displayName = title; + return new Image(this, mContentResolver, id, cursor.getPosition(), + contentUri(id), dataPath, miniThumbMagic, mimeType, dateTaken, + title, displayName, orientation); } - private String sortOrder() { + protected 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 diff --git a/src/com/android/camera/gallery/ImageListUber.java b/src/com/android/camera/gallery/ImageListUber.java index 1ffd94b..d5e96c5 100644 --- a/src/com/android/camera/gallery/ImageListUber.java +++ b/src/com/android/camera/gallery/ImageListUber.java @@ -17,30 +17,51 @@ package com.android.camera.gallery; import com.android.camera.ImageManager; +import com.android.camera.Util; import android.net.Uri; import java.io.IOException; -import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; import java.util.HashMap; +import java.util.PriorityQueue; /** * A union of different <code>IImageList</code>. */ +//TODO: add unittest for this class public class ImageListUber implements IImageList { @SuppressWarnings("unused") private static final String TAG = "ImageListUber"; private final IImageList [] mSubList; - private final int mSort; - - // 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). - private ArrayList<Long> mSkipList = null; + private final PriorityQueue<MergeSlot> mQueue; + + // This is an array of Longs wherein each Long consists of two components: + // "a number" and "an index of sublist". + // * The lower 32bit indicates the number of consecutive entries that + // belong to a given sublist. + // + // * The higher 32bit component indicates which sublist we're referring + // to. + private long[] mSkipList = new long[16]; + private int mSkipListSize = 0; private int [] mSkipCounts = null; + private int mLastListIndex = -1; + + public ImageListUber(IImageList [] sublist, int sort) { + mSubList = sublist.clone(); + mSkipCounts = new int[mSubList.length]; + mQueue = new PriorityQueue<MergeSlot>(4, + sort == ImageManager.SORT_ASCENDING + ? new AscendingComparator() + : new DescendingComparator()); + for (int i = 0, n = sublist.length; i < n; ++i) { + MergeSlot slot = new MergeSlot(sublist[i], i); + if (slot.next()) mQueue.add(slot); + } + } public HashMap<String, String> getBucketIds() { HashMap<String, String> hashMap = new HashMap<String, String>(); @@ -50,47 +71,37 @@ public class ImageListUber implements IImageList { return hashMap; } - public ImageListUber(IImageList [] sublist, int sort) { - mSubList = sublist.clone(); - mSort = sort; - } - public void checkThumbnail(int index) throws IOException { + // The index is not refer to the index of the image but in another order + // sequence. Since this function is only used to check all thumbnails + // is created, it should be fine. for (IImageList list : mSubList) { int count = list.getCount(); if (count > index) { list.checkThumbnail(index); + return; } index -= count; } } public void deactivate() { - final IImageList sublist[] = mSubList; - final int length = sublist.length; - int pos = -1; - while (++pos < length) { - IImageList sub = sublist[pos]; - sub.deactivate(); + for (IImageList subList : mSubList) { + subList.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(); + for (IImageList subList : mSubList) { + count += subList.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; - } + for (IImageList subList : mSubList) { + if (!subList.isEmpty()) return false; } return true; } @@ -106,20 +117,10 @@ public class ImageListUber implements IImageList { "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>(); - } - + int skipCounts[] = mSkipCounts; // 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; - } + Arrays.fill(skipCounts, 0); // a counter of how many images we've skipped in // trying to get to index. alternatively we could @@ -129,87 +130,53 @@ public class ImageListUber implements IImageList { // 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); + for (int i = 0, n = mSkipListSize; i < n; ++i) { + long v = mSkipList[i]; int offset = (int) (v & 0xFFFFFFFF); int which = (int) (v >> 32); - if (skipCount + offset > index) { int subindex = mSkipCounts[which] + (index - skipCount); - IImage img = mSubList[which].getImageAt(subindex); - return img; + return mSubList[which].getImageAt(subindex); } - 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. - while (true) { - // We are merging the sublists into this uber list. - // We pick the next image by choosing the one with - // max/min timestamp from the next image of each sublists. - // Then we record this fact in mSkipList (which encodes - // sublist number in a run-length encoding fashion). - long maxTimestamp = mSort == ImageManager.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 == ImageManager.SORT_ASCENDING - ? (timestamp < maxTimestamp) - : (timestamp > maxTimestamp)) { - maxTimestamp = timestamp; - which = i; - } - } - } - } - - if (which == -1) { - return null; - } - - boolean done = false; - 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) | 1; // initial count = 1 - mSkipList.add(newEntry); + for (;true; ++skipCount) { + MergeSlot slot = nextMergeSlot(); + if (slot == null) return null; + if (skipCount == index) { + IImage result = slot.mImage; + if (slot.next()) mQueue.add(slot); + return result; } + if (slot.next()) mQueue.add(slot); + } + } - if (skipCount++ == index) { - return mSubList[which].getImageAt(mSkipCounts[which]); + private MergeSlot nextMergeSlot() { + MergeSlot slot = mQueue.poll(); + if (slot == null) return null; + if (slot.mListIndex == mLastListIndex) { + int lastIndex = mSkipListSize - 1; + ++ mSkipList[lastIndex]; + } else { + mLastListIndex = slot.mListIndex; + if (mSkipList.length == mSkipListSize) { + long [] temp = new long[mSkipListSize * 2]; + System.arraycopy(mSkipList, 0, temp, 0, mSkipListSize); + mSkipList = temp; } - mSkipCounts[which] += 1; + mSkipList[mSkipListSize++] = (((long) mLastListIndex) << 32) | 1; } + return slot; } 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; + for (IImageList sublist : mSubList) { + IImage image = sublist.getImageForUri(uri); + if (image != null) return image; } return null; } @@ -220,51 +187,111 @@ public class ImageListUber implements IImageList { * counter. This is simple because deletion can never * cause change the order of images. */ - public void modifySkipCountForDeletedImage(int index) { + private void modifySkipCountForDeletedImage(int index) { int skipCount = 0; - - for (int i = 0; i < mSkipList.size(); i++) { - long v = mSkipList.get(i); - + for (int i = 0, n = mSkipListSize; i < n; i++) { + long v = mSkipList[i]; int offset = (int) (v & 0xFFFFFFFF); int which = (int) (v >> 32); - if (skipCount + offset > index) { - mSkipList.set(i, v - 1); + mSkipList[i] = v - 1; break; } - skipCount += offset; } } + private boolean removeImage(IImage image, int index) { + IImageList list = image.getContainer(); + if (list != null && list.removeImage(image)) { + modifySkipCountForDeletedImage(index); + return true; + } + return false; + } + 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; + return removeImage(image, getImageIndex(image)); + } + + public boolean removeImageAt(int index) { + IImage image = getImageAt(index); + if (image == null) return false; + return removeImage(image, index); + } + + public synchronized int getImageIndex(IImage image) { + IImageList list = image.getContainer(); + int listIndex = Util.indexOf(mSubList, list); + if (listIndex == -1) { + throw new IllegalArgumentException(); + } + int listOffset = list.getImageIndex(image); + + // Similar algorithm as getImageAt(int index) + int skipCount = 0; + for (int i = 0, n = mSkipListSize; i < n; ++i) { + long value = mSkipList[i]; + int offset = (int) (value & 0xFFFFFFFF); + int which = (int) (value >> 32); + if (which == listIndex) { + if (listOffset < offset) { + return skipCount + listOffset; } + listOffset -= offset; } - baseIndex += sub.getCount(); + skipCount += offset; + } + + for (;true; ++skipCount) { + MergeSlot slot = nextMergeSlot(); + if (slot == null) return -1; + if (slot.mImage == image) { + if (slot.next()) mQueue.add(slot); + return skipCount; + } + if (slot.next()) mQueue.add(slot); + } + } + + private static class DescendingComparator implements Comparator<MergeSlot> { + + public int compare(MergeSlot m1, MergeSlot m2) { + if (m1.mDateTaken != m2.mDateTaken) { + return m1.mDateTaken < m2.mDateTaken ? -1 : 1; + } + return m1.mListIndex - m2.mListIndex; } - 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); + private static class AscendingComparator implements Comparator<MergeSlot> { + + public int compare(MergeSlot m1, MergeSlot m2) { + if (m1.mDateTaken != m2.mDateTaken) { + return m1.mDateTaken < m2.mDateTaken ? -1 : 1; } + return m1.mListIndex - m2.mListIndex; + } + } + + private static class MergeSlot { + private int mOffset = -1; + private final IImageList mList; + + int mListIndex; + long mDateTaken; + IImage mImage; + + public MergeSlot(IImageList list, int index) { + mList = list; + mListIndex = index; + } + + public boolean next() { + if (mOffset >= mList.getCount() - 1) return false; + mImage = mList.getImageAt(++mOffset); + mDateTaken = mImage.getDateTaken(); + return true; } } } diff --git a/src/com/android/camera/gallery/LruCache.java b/src/com/android/camera/gallery/LruCache.java new file mode 100644 index 0000000..fb18c99 --- /dev/null +++ b/src/com/android/camera/gallery/LruCache.java @@ -0,0 +1,19 @@ +package com.android.camera.gallery; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class LruCache<K, V> extends LinkedHashMap<K, V> { + + private final int mCapacity; + + public LruCache(int capacity) { + super(16, 0.75f, true); + this.mCapacity = capacity; + } + + @Override + protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { + return size() > mCapacity; + } +} diff --git a/src/com/android/camera/gallery/SingleImageList.java b/src/com/android/camera/gallery/SingleImageList.java index 026cbee..6feca73 100644 --- a/src/com/android/camera/gallery/SingleImageList.java +++ b/src/com/android/camera/gallery/SingleImageList.java @@ -19,6 +19,7 @@ package com.android.camera.gallery; import com.android.camera.ImageManager; import android.content.ContentResolver; +import android.database.Cursor; import android.net.Uri; import java.util.HashMap; @@ -27,7 +28,8 @@ import java.util.HashMap; * An implementation of interface <code>IImageList</code> which contains only * one image. */ -public class SingleImageList extends BaseImageList implements IImageList { +// TODO: consider implements not extends +public class SingleImageList extends BaseImageList { @SuppressWarnings("unused") private static final String TAG = "BaseImageList"; @@ -59,52 +61,28 @@ public class SingleImageList extends BaseImageList implements IImageList { } @Override - public IImage getImageAt(int i) { - return i == 0 ? mSingleImage : null; - } - - @Override - public IImage getImageForUri(Uri uri) { - return uri.equals(mBaseUri) ? mSingleImage : null; - } - - @Override - protected int indexId() { - return -1; - } - - @Override - protected int indexData() { - return -1; - } - - @Override - protected int indexMimeType() { - return -1; + protected long getImageId(Cursor cursor) { + throw new UnsupportedOperationException(); } - @Override - protected int indexDateTaken() { - return -1; - } @Override - protected int indexMiniThumbMagic() { - return -1; + public IImage getImageAt(int i) { + return i == 0 ? mSingleImage : null; } @Override - protected int indexOrientation() { - return -1; + public IImage getImageForUri(Uri uri) { + return uri.equals(mBaseUri) ? mSingleImage : null; } @Override - protected int indexTitle() { - return -1; + protected Cursor createCursor() { + throw new UnsupportedOperationException(); } @Override - protected int indexDisplayName() { - return -1; + protected BaseImage loadImageFromCursor(Cursor cursor) { + throw new UnsupportedOperationException(); } } diff --git a/src/com/android/camera/gallery/UriImage.java b/src/com/android/camera/gallery/UriImage.java index dd62c39..8a8415e 100644 --- a/src/com/android/camera/gallery/UriImage.java +++ b/src/com/android/camera/gallery/UriImage.java @@ -221,10 +221,6 @@ class UriImage implements IImage { return false; } - public void onRemove() { - throw new UnsupportedOperationException(); - } - public boolean rotateImageBy(int degrees) { return false; } diff --git a/src/com/android/camera/gallery/VideoList.java b/src/com/android/camera/gallery/VideoList.java index e95aa02..9f40dc2 100644 --- a/src/com/android/camera/gallery/VideoList.java +++ b/src/com/android/camera/gallery/VideoList.java @@ -17,69 +17,62 @@ package com.android.camera.gallery; import com.android.camera.ImageManager; -import com.android.camera.Util; import android.content.ContentResolver; import android.database.Cursor; import android.net.Uri; import android.provider.MediaStore.Images; -import android.provider.MediaStore.Video; -import android.provider.MediaStore.Video.VideoColumns; -import android.util.Log; +import android.provider.MediaStore.Video.Media; import java.util.HashMap; /** * A collection of all the <code>VideoObject</code> in gallery. */ -public class VideoList extends BaseImageList implements IImageList { +public class VideoList extends BaseImageList { + + @SuppressWarnings("unused") private static final String TAG = "BaseImageList"; private static final String[] VIDEO_PROJECTION = new String[] { - Video.Media._ID, - Video.Media.DATA, - Video.Media.DATE_TAKEN, - Video.Media.TITLE, - Video.Media.DISPLAY_NAME, - Video.Media.TAGS, - Video.Media.CATEGORY, - Video.Media.LANGUAGE, - Video.Media.MINI_THUMB_MAGIC, - Video.Media.MIME_TYPE}; - - private static final int INDEX_ID = indexOf(Video.Media._ID); - private static final int INDEX_DATA = indexOf(Video.Media.DATA); - private static final int INDEX_DATE_TAKEN = indexOf(Video.Media.DATE_TAKEN); - private static final int INDEX_TITLE = indexOf(Video.Media.TITLE); - private static final int INDEX_DISPLAY_NAME = - indexOf(Video.Media.DISPLAY_NAME); - private static final int INDEX_MIME_TYPE = indexOf(Video.Media.MIME_TYPE); - private static final int INDEX_MINI_THUMB_MAGIC = - indexOf(Video.Media.MINI_THUMB_MAGIC); - - private static int indexOf(String field) { - return Util.indexOf(VIDEO_PROJECTION, field); + Media._ID, + Media.DATA, + Media.DATE_TAKEN, + Media.TITLE, + Media.DISPLAY_NAME, + Media.MINI_THUMB_MAGIC, + Media.MIME_TYPE}; + + private static final int INDEX_ID = 0; + private static final int INDEX_DATA_PATH = 1; + private static final int INDEX_DATE_TAKEN = 2; + private static final int INDEX_TITLE = 3; + private static final int INDEX_DISPLAY_NAME = 4; + private static final int INDEX_MIMI_THUMB_MAGIC = 5; + private static final int INDEX_MIME_TYPE = 6; + + @Override + protected long getImageId(Cursor cursor) { + return cursor.getLong(INDEX_ID); } - public VideoList(ContentResolver cr, Uri uri, Uri thumbUri, - int sort, String bucketId) { - super(cr, uri, sort, bucketId); + @Override + protected BaseImage loadImageFromCursor(Cursor cursor) { + long id = cursor.getLong(INDEX_ID); + String dataPath = cursor.getString(INDEX_DATA_PATH); + long dateTaken = cursor.getLong(INDEX_DATE_TAKEN); + String title = cursor.getString(INDEX_TITLE); + String displayName = cursor.getString(INDEX_DISPLAY_NAME); + long miniThumbMagic = cursor.getLong(INDEX_MIMI_THUMB_MAGIC); + String mimeType = cursor.getString(INDEX_MIME_TYPE); - mCursor = createCursor(); - if (mCursor == null) { - Log.e(TAG, "unable to create video cursor for " + mBaseUri); - throw new UnsupportedOperationException(); - } + return new VideoObject(this, mContentResolver, + id, cursor.getPosition(), contentUri(id), dataPath, + miniThumbMagic, mimeType, dateTaken, title, displayName); + } - if (mCursor.moveToFirst()) { - int row = 0; - do { - long imageId = mCursor.getLong(indexId()); - long miniThumbMagic = mCursor.getLong(indexMiniThumbMagic()); - mCache.put(imageId, new VideoObject(imageId, miniThumbMagic, - mContentResolver, this, row++)); - } while (mCursor.moveToNext()); - } + public VideoList(ContentResolver cr, Uri uri, int sort, String bucketId) { + super(cr, uri, sort, bucketId); } public HashMap<String, String> getBucketIds() { @@ -88,31 +81,32 @@ public class VideoList extends BaseImageList implements IImageList { Cursor c = Images.Media.query( mContentResolver, uri, new String[] { - VideoColumns.BUCKET_DISPLAY_NAME, - VideoColumns.BUCKET_ID + Media.BUCKET_DISPLAY_NAME, + Media.BUCKET_ID }, whereClause(), whereClauseArgs(), sortOrder()); - HashMap<String, String> hash = new HashMap<String, String>(); - if (c != null && c.moveToFirst()) { - do { + try { + HashMap<String, String> hash = new HashMap<String, String>(); + while (c.moveToNext()) { hash.put(c.getString(1), c.getString(0)); - } while (c.moveToNext()); + } + return hash; + } finally { + c.close(); } - return hash; } protected String whereClause() { - if (mBucketId != null) { - return Images.Media.BUCKET_ID + " = '" + mBucketId + "'"; - } else { - return null; - } + return mBucketId != null + ? Images.Media.BUCKET_ID + " = '" + mBucketId + "'" + : null; } protected String[] whereClauseArgs() { return null; } + @Override protected Cursor createCursor() { Cursor c = Images.Media.query( mContentResolver, mBaseUri, VIDEO_PROJECTION, @@ -120,55 +114,8 @@ public class VideoList extends BaseImageList implements IImageList { return c; } - @Override - protected int indexId() { - return INDEX_ID; - } - - @Override - protected int indexData() { - return INDEX_DATA; - } - - @Override - protected int indexMimeType() { - return INDEX_MIME_TYPE; - } - - @Override - protected int indexDateTaken() { - return INDEX_DATE_TAKEN; - } - - @Override - protected int indexMiniThumbMagic() { - return INDEX_MINI_THUMB_MAGIC; - } - - @Override - protected int indexOrientation() { - return -1; - } - - @Override - protected int indexTitle() { - return INDEX_TITLE; - } - - @Override - protected int indexDisplayName() { - return -1; - } - - @Override - protected IImage make(long id, long miniThumbMagic, ContentResolver cr, - IImageList list, int index, int rotation) { - return new VideoObject(id, miniThumbMagic, mContentResolver, this, - index); - } - private String sortOrder() { - return Video.Media.DATE_TAKEN + + return Media.DATE_TAKEN + (mSort == ImageManager.SORT_ASCENDING ? " ASC " : " DESC"); } }
\ No newline at end of file diff --git a/src/com/android/camera/gallery/VideoObject.java b/src/com/android/camera/gallery/VideoObject.java index a5ed17e..5da63bf 100644 --- a/src/com/android/camera/gallery/VideoObject.java +++ b/src/com/android/camera/gallery/VideoObject.java @@ -19,8 +19,8 @@ package com.android.camera.gallery; import com.android.camera.ImageManager; import android.content.ContentResolver; -import android.database.Cursor; import android.graphics.Bitmap; +import android.net.Uri; import java.io.IOException; import java.io.InputStream; @@ -38,9 +38,11 @@ public class VideoObject extends BaseImage implements IImage { * @param id the image id of the image * @param cr the content resolver */ - protected VideoObject(long id, long miniThumbMagic, ContentResolver cr, - VideoList container, int row) { - super(id, miniThumbMagic, cr, container, row); + protected VideoObject(BaseImageList container, ContentResolver cr, + long id, int index, Uri uri, String dataPath, long miniThumbMagic, + String mimeType, long dateTaken, String title, String displayName) { + super(container, cr, id, index, uri, dataPath, miniThumbMagic, + mimeType, dateTaken, title, displayName); } @Override @@ -60,18 +62,6 @@ public class VideoObject extends BaseImage implements IImage { return fullSizeImageUri().toString().hashCode(); } - 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; - } - @Override public Bitmap fullSizeBitmap(int targetWidthHeight) { return ImageManager.NO_IMAGE_BITMAP; @@ -127,8 +117,6 @@ public class VideoObject extends BaseImage implements IImage { @Override public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("" + mId); - return sb.toString(); + return new StringBuilder("VideoObject").append(mId).toString(); } }
\ No newline at end of file |