--- /dev/null
+package com.cooliris.media;
+
+import java.io.File;
+import java.net.URI;
+import java.util.ArrayList;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.Handler;
+import android.provider.MediaStore.Images;
+import android.provider.MediaStore.Video;
+import android.util.Log;
+
+import com.cooliris.cache.CacheService;
+
+public final class LocalDataSource implements DataSource {
+ private static final String TAG = "LocalDataSource";
+
+ public static final DiskCache sThumbnailCache = new DiskCache("local-image-thumbs");
+ public static final DiskCache sThumbnailCacheVideo = new DiskCache("local-video-thumbs");
+
+ public static final String CAMERA_STRING = "Camera";
+ public static final String DOWNLOAD_STRING = "download";
+ public static final String CAMERA_BUCKET_NAME = Environment.getExternalStorageDirectory().toString() + "/DCIM/" + CAMERA_STRING;
+ public static final String DOWNLOAD_BUCKET_NAME = Environment.getExternalStorageDirectory().toString() + "/" + DOWNLOAD_STRING;
+ public static final int CAMERA_BUCKET_ID = getBucketId(CAMERA_BUCKET_NAME);
+ public static final int DOWNLOAD_BUCKET_ID = getBucketId(DOWNLOAD_BUCKET_NAME);
+
+ public static boolean sObserverActive = false;
+ private boolean mDisableImages;
+ private boolean mDisableVideos;
+
+ /**
+ * Matches code in MediaProvider.computeBucketValues. Should be a common
+ * function.
+ */
+ public static int getBucketId(String path) {
+ return (path.toLowerCase().hashCode());
+ }
+
+ private Context mContext;
+ private ContentObserver mObserver;
+
+ public LocalDataSource(Context context) {
+ mContext = context;
+ }
+
+ public void setMimeFilter(boolean disableImages, boolean disableVideos) {
+ mDisableImages = disableImages;
+ mDisableVideos = disableVideos;
+ }
+
+ public void loadMediaSets(final MediaFeed feed) {
+ if (mContext == null) {
+ return;
+ }
+ stopListeners();
+ CacheService.loadMediaSets(feed, this, !mDisableImages, !mDisableVideos);
+ Handler handler = ((Gallery) mContext).getHandler();
+ ContentObserver observer = new ContentObserver(handler) {
+ public void onChange(boolean selfChange) {
+ final boolean isPaused = ((Gallery) mContext).isPaused();
+ if (isPaused) {
+ MediaSet mediaSet = feed.getCurrentSet();
+ if (mediaSet != null) {
+ CacheService.markDirtyImmediate(mediaSet.mId);
+ refreshUI(feed, mediaSet.mId);
+ }
+ }
+ CacheService.senseDirty(mContext, new CacheService.Observer() {
+ public void onChange(long[] ids) {
+ if (!isPaused)
+ return;
+ if (ids != null) {
+ int numLongs = ids.length;
+ for (int i = 0; i < numLongs; ++i) {
+ refreshUI(feed, ids[i]);
+ }
+ }
+ }
+ });
+ }
+ };
+
+ // Start listening.
+ Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
+ Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI;
+ ContentResolver cr = mContext.getContentResolver();
+ mObserver = observer;
+ cr.registerContentObserver(uriImages, true, observer);
+ cr.registerContentObserver(uriVideos, true, observer);
+ sObserverActive = true;
+ }
+
+ public void shutdown() {
+ stopListeners();
+ }
+
+ private void stopListeners() {
+ ContentResolver cr = mContext.getContentResolver();
+ if (mObserver != null) {
+ cr.unregisterContentObserver(mObserver);
+ cr.unregisterContentObserver(mObserver);
+ }
+ sObserverActive = false;
+ }
+
+ protected void refreshUI(MediaFeed feed, long setIdToUse) {
+ if (setIdToUse == Shared.INVALID) {
+ return;
+ }
+ if (feed.getMediaSet(setIdToUse) == null) {
+ MediaSet mediaSet = feed.addMediaSet(setIdToUse, this);
+ if (setIdToUse == CAMERA_BUCKET_ID) {
+ mediaSet.mName = CAMERA_STRING;
+ } else if (setIdToUse == DOWNLOAD_BUCKET_ID) {
+ mediaSet.mName = DOWNLOAD_STRING;
+ }
+ mediaSet.generateTitle(true);
+ } else {
+ MediaSet mediaSet = feed.replaceMediaSet(setIdToUse, this);
+ Log.i(TAG, "Replacing mediaset " + mediaSet.mName + " id " + setIdToUse + " current Id " + mediaSet.mId);
+ if (setIdToUse == CAMERA_BUCKET_ID) {
+ mediaSet.mName = CAMERA_STRING;
+ } else if (setIdToUse == DOWNLOAD_BUCKET_ID) {
+ mediaSet.mName = DOWNLOAD_STRING;
+ }
+ mediaSet.generateTitle(true);
+ }
+ }
+
+ public void loadItemsForSet(final MediaFeed feed, final MediaSet parentSet, int rangeStart, int rangeEnd) {
+ // Quick load from the cache.
+ if (mContext == null || parentSet == null) {
+ return;
+ }
+ loadMediaItemsIntoMediaFeed(feed, parentSet, rangeStart, rangeEnd);
+ }
+
+ private void loadMediaItemsIntoMediaFeed(final MediaFeed mediaFeed, final MediaSet set, int rangeStart, int rangeEnd) {
+ if (rangeEnd - rangeStart < 0) {
+ return;
+ }
+ CacheService.loadMediaItemsIntoMediaFeed(mediaFeed, set, rangeStart, rangeEnd, !mDisableImages, !mDisableVideos);
+ if (set.mId == CAMERA_BUCKET_ID) {
+ mediaFeed.moveSetToFront(set);
+ }
+ }
+
+ public boolean performOperation(final int operation, final ArrayList<MediaBucket> mediaBuckets, final Object data) {
+ int numBuckets = mediaBuckets.size();
+ ContentResolver cr = mContext.getContentResolver();
+ switch (operation) {
+ case MediaFeed.OPERATION_DELETE:
+ for (int i = 0; i < numBuckets; ++i) {
+ MediaBucket bucket = mediaBuckets.get(i);
+ MediaSet set = bucket.mediaSet;
+ ArrayList<MediaItem> items = bucket.mediaItems;
+ if (set != null && items == null) {
+ // Remove the entire bucket.
+ final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
+ final Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI;
+ final String whereImages = Images.ImageColumns.BUCKET_ID + "=" + Long.toString(set.mId);
+ final String whereVideos = Video.VideoColumns.BUCKET_ID + "=" + Long.toString(set.mId);
+ cr.delete(uriImages, whereImages, null);
+ cr.delete(uriVideos, whereVideos, null);
+ CacheService.markDirty(mContext);
+ }
+ if (set != null && items != null) {
+ // We need to remove these items from the set.
+ int numItems = items.size();
+ try {
+ for (int j = 0; j < numItems; ++j) {
+ MediaItem item = items.get(j);
+ cr.delete(Uri.parse(item.mContentUri), null, null);
+ }
+ } catch (Exception e) {
+ // If the database operation failed for any reason.
+ ;
+ }
+ set.updateNumExpectedItems();
+ set.generateTitle(true);
+ CacheService.markDirty(mContext, set.mId);
+ }
+ }
+ break;
+ case MediaFeed.OPERATION_ROTATE:
+ for (int i = 0; i < numBuckets; ++i) {
+ MediaBucket bucket = mediaBuckets.get(i);
+ ArrayList<MediaItem> items = bucket.mediaItems;
+ if (items == null) {
+ continue;
+ }
+ float angleToRotate = ((Float) data).floatValue();
+ if (angleToRotate == 0) {
+ return true;
+ }
+ int numItems = items.size();
+ for (int j = 0; j < numItems; ++j) {
+ rotateItem(items.get(j), angleToRotate);
+ }
+ }
+ break;
+ }
+ return true;
+ }
+
+ private void rotateItem(final MediaItem item, float angleToRotate) {
+ ContentResolver cr = mContext.getContentResolver();
+ try {
+ int currentOrientation = (int) item.mRotation;
+ angleToRotate += currentOrientation;
+ float rotation = Shared.normalizePositive(angleToRotate);
+ String rotationString = Integer.toString((int) rotation);
+
+ // Update the database entry.
+ ContentValues values = new ContentValues();
+ values.put(Images.ImageColumns.ORIENTATION, rotationString);
+ try {
+ cr.update(Uri.parse(item.mContentUri), values, null, null);
+ } catch (Exception e) {
+ // If the database operation fails for any reason.
+ ;
+ }
+
+ // Update the file EXIF information.
+ Uri uri = Uri.parse(item.mContentUri);
+ String uriScheme = uri.getScheme();
+ if (uriScheme.equals("file")) {
+ ExifInterface exif = new ExifInterface(uri.getPath());
+ exif.setAttribute(ExifInterface.TAG_ORIENTATION, Integer.toString(Shared.degreesToExifOrientation(rotation)));
+ exif.saveAttributes();
+ }
+
+ // Invalidate the cache entry.
+ CacheService.markDirty(mContext, item.mParentMediaSet.mId);
+
+ // Update the object representation of the item.
+ item.mRotation = rotation;
+ } catch (Exception e) {
+ // System.out.println("Apparently not a JPEG");
+ }
+ }
+
+ public DiskCache getThumbnailCache() {
+ return sThumbnailCache;
+ }
+
+ public static MediaItem createMediaItemFromUri(Context context, Uri target) {
+ MediaItem item = null;
+ long id = ContentUris.parseId(target);
+ ContentResolver cr = context.getContentResolver();
+ String whereClause = Images.ImageColumns._ID + "=" + Long.toString(id);
+ try {
+ Cursor cursor = cr.query(Images.Media.EXTERNAL_CONTENT_URI, CacheService.PROJECTION_IMAGES, whereClause, null, null);
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ item = new MediaItem();
+ CacheService.populateMediaItemFromCursor(item, cr, cursor, Images.Media.EXTERNAL_CONTENT_URI.toString() + "/");
+ }
+ cursor.close();
+ cursor = null;
+ }
+ } catch (Exception e) {
+ // If the database operation failed for any reason.
+ ;
+ }
+ return item;
+ }
+
+ public static MediaItem createMediaItemFromFileUri(Context context, String fileUri) {
+ MediaItem item = null;
+ String filepath = new File(URI.create(fileUri)).toString();
+ ContentResolver cr = context.getContentResolver();
+ long bucketId = SingleDataSource.parseBucketIdFromFileUri(fileUri);
+ String whereClause = Images.ImageColumns.BUCKET_ID + "=" + bucketId + " AND " + Images.ImageColumns.DATA + "='" + filepath
+ + "'";
+ try {
+ Cursor cursor = cr.query(Images.Media.EXTERNAL_CONTENT_URI, CacheService.PROJECTION_IMAGES, whereClause, null, null);
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ item = new MediaItem();
+ CacheService.populateMediaItemFromCursor(item, cr, cursor, Images.Media.EXTERNAL_CONTENT_URI.toString() + "/");
+ }
+ cursor.close();
+ cursor = null;
+ }
+ } catch (Exception e) {
+ // If the database operation failed for any reason.
+ ;
+ }
+ return item;
+ }
+}