/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.cooliris.media;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.provider.MediaStore.Images;
import android.provider.MediaStore.Video;
import android.util.Log;
import com.cooliris.cache.CacheService;
public class LocalDataSource implements DataSource {
private static final String TAG = "LocalDataSource";
public static final String URI_ALL_MEDIA = MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString();
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);
/**
* Matches code in MediaProvider.computeBucketValues. Should be a common
* function.
*/
public static int getBucketId(String path) {
return (path.toLowerCase().hashCode());
}
private final String mUri;
private final String mBucketId;
private boolean mDone;;
private final boolean mSingleUri;
private final boolean mAllItems;
private final boolean mFlattenAllItems;
private final DiskCache mDiskCache;
private boolean mIncludeImages;
private boolean mIncludeVideos;
private Context mContext;
public LocalDataSource(final Context context, final String uri, final boolean flattenAllItems) {
this.mUri = uri;
mContext = context;
mIncludeImages = true;
mIncludeVideos = false;
String bucketId = Uri.parse(uri).getQueryParameter("bucketId");
if (bucketId != null && bucketId.length() > 0) {
mBucketId = bucketId;
} else {
mBucketId = null;
}
mFlattenAllItems = flattenAllItems;
if (mBucketId == null) {
if (uri.equals(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString())) {
mAllItems = true;
} else {
mAllItems = false;
}
} else {
mAllItems = false;
}
mSingleUri = isSingleImageMode(uri) && mBucketId == null;
mDone = false;
mDiskCache = mUri.startsWith(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString())
|| mUri.startsWith(MediaStore.Video.Media.EXTERNAL_CONTENT_URI.toString())
|| mUri.startsWith("file://") ? sThumbnailCache
: null;
}
public void setMimeFilter(boolean includeImages, boolean includeVideos) {
mIncludeImages = includeImages;
mIncludeVideos = includeVideos;
}
public void shutdown() {
}
public boolean isSingleImage() {
return mSingleUri;
}
private static boolean isSingleImageMode(String uriString) {
return !uriString.equals(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString())
&& !uriString.equals(MediaStore.Images.Media.INTERNAL_CONTENT_URI.toString());
}
public DiskCache getThumbnailCache() {
return mDiskCache;
}
public void loadItemsForSet(MediaFeed feed, MediaSet parentSet, int rangeStart, int rangeEnd) {
if (parentSet.mNumItemsLoaded > 0 && mDone) {
return;
}
if (mSingleUri && !mDone) {
MediaItem item = new MediaItem();
item.mId = 0;
item.mFilePath = "";
item.setMediaType((isImage(mUri)) ? MediaItem.MEDIA_TYPE_IMAGE : MediaItem.MEDIA_TYPE_VIDEO);
if (mUri.startsWith(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString())
|| mUri.startsWith(MediaStore.Video.Media.EXTERNAL_CONTENT_URI.toString())) {
MediaItem newItem = createMediaItemFromUri(mContext, Uri.parse(mUri), item.getMediaType());
if (newItem != null) {
item = newItem;
String fileUri = new File(item.mFilePath).toURI().toString();
parentSet.mName = Utils.getBucketNameFromUri(mContext.getContentResolver(), Uri.parse(fileUri));
parentSet.mId = Utils.getBucketIdFromUri(mContext.getContentResolver(), Uri.parse(fileUri));
parentSet.generateTitle(true);
}
} else if (mUri.startsWith("file://")) {
MediaItem newItem = null;
int numRetries = 15;
do {
newItem = createMediaItemFromFileUri(mContext, mUri);
if (newItem == null) {
--numRetries;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
;
}
}
} while (newItem == null && numRetries >= 0);
if (newItem != null) {
item = newItem;
} else {
item.mContentUri = mUri;
item.mThumbnailUri = mUri;
item.mScreennailUri = mUri;
feed.setSingleImageMode(true);
}
} else {
item.mContentUri = mUri;
item.mThumbnailUri = mUri;
item.mScreennailUri = mUri;
feed.setSingleImageMode(true);
}
if (item != null) {
feed.addItemToMediaSet(item, parentSet);
// Parse EXIF orientation if a local file.
if (mUri.startsWith("file://")) {
try {
ExifInterface exif = new ExifInterface(Uri.parse(mUri).getPath());
item.mRotation = Shared.exifOrientationToDegrees(exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL));
} catch (IOException e) {
Log.i(TAG, "Error reading Exif information, probably not a jpeg.");
}
}
// Try and get the date taken for this item.
long dateTaken = CacheService.fetchDateTaken(item);
if (dateTaken != -1L) {
item.mDateTakenInMs = dateTaken;
}
CacheService.loadMediaItemsIntoMediaFeed(mContext, feed, parentSet, rangeStart, rangeEnd, mIncludeImages, mIncludeVideos);
ArrayList<MediaItem> items = parentSet.getItems();
int numItems = items.size();
if (numItems == 1 && parentSet.mNumItemsLoaded > 1) {
parentSet.mNumItemsLoaded = 1;
}
parentSet.removeDuplicate(item);
}
parentSet.updateNumExpectedItems();
parentSet.generateTitle(true);
} else if (mUri.equals(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString()) & mFlattenAllItems) {
final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
final ContentResolver cr = mContext.getContentResolver();
String where = null;
try {
Cursor cursor = cr.query(uriImages, CacheService.PROJECTION_IMAGES, where, null, null);
if (cursor != null && cursor.moveToFirst()) {
parentSet.setNumExpectedItems(cursor.getCount());
do {
if (Thread.interrupted()) {
return;
}
final MediaItem item = new MediaItem();
CacheService.populateMediaItemFromCursor(item, cr, cursor, CacheService.BASE_CONTENT_STRING_IMAGES);
feed.addItemToMediaSet(item, parentSet);
} while (cursor.moveToNext());
if (cursor != null) {
cursor.close();
cursor = null;
}
parentSet.updateNumExpectedItems();
parentSet.generateTitle(true);
}
} catch (Exception e) {
// If the database operation failed for any reason.
;
}
} else {
CacheService.loadMediaItemsIntoMediaFeed(mContext, feed, parentSet, rangeStart, rangeEnd, mIncludeImages, mIncludeVideos);
}
mDone = true;
}
private static boolean isImage(String uriString) {
return !uriString.startsWith(MediaStore.Video.Media.EXTERNAL_CONTENT_URI.toString());
}
public void loadMediaSets(final MediaFeed feed) {
MediaSet set = null; // Dummy set.
boolean loadOtherSets = true;
if (mSingleUri) {
String name = Utils.getBucketNameFromUri(mContext.getContentResolver(), Uri.parse(mUri));
long id = Utils.getBucketIdFromUri(mContext.getContentResolver(), Uri.parse(mUri));
set = feed.addMediaSet(id, this);
set.mName = name;
set.mId = id;
set.setNumExpectedItems(2);
set.generateTitle(true);
set.mPicasaAlbumId = Shared.INVALID;
if (this.getThumbnailCache() != sThumbnailCache) {
loadOtherSets = false;
}
} else if (mBucketId == null) {
// All the buckets.
if (mFlattenAllItems) {
set = feed.addMediaSet(0, this); // Create dummy set.
set.mName = Utils.getBucketNameFromUri(mContext.getContentResolver(), Uri.parse(mUri));
set.mId = getBucketId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString() + "/" + set.mName);
set.setNumExpectedItems(1);
set.generateTitle(true);
set.mPicasaAlbumId = Shared.INVALID;
} else {
CacheService.loadMediaSets(mContext, feed, this, mIncludeImages, mIncludeVideos, true);
}
} else {
CacheService.loadMediaSet(mContext, feed, this, Long.parseLong(mBucketId));
ArrayList<MediaSet> sets = feed.getMediaSets();
if (sets.size() > 0)
set = sets.get(0);
}
// We also load the other MediaSets
if (!mAllItems && set != null && loadOtherSets) {
final long setId = set.mId;
if (!CacheService.isPresentInCache(setId)) {
CacheService.markDirty();
}
CacheService.loadMediaSets(mContext, feed, this, mIncludeImages, mIncludeVideos, false);
// not re-ordering media sets in the case of displaying a single image
if (!mSingleUri) {
feed.moveSetToFront(set);
}
}
}
public boolean performOperation(int operation, ArrayList<MediaBucket> mediaBuckets, 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) {
// TODO bulk delete
// 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();
}
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(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") || uriScheme.equals("content")) {
final String path = (uriScheme.equals("file")) ? uri.getPath() : item.mFilePath;
ExifInterface exif = new ExifInterface(path);
exif.setAttribute(ExifInterface.TAG_ORIENTATION, Integer.toString(Shared.degreesToExifOrientation(rotation)));
exif.saveAttributes();
}
// Invalidate the cache entry.
CacheService.markDirty(item.mParentMediaSet.mId);
// Update the object representation of the item.
item.mRotation = rotation;
} catch (Exception e) {
// System.out.println("Apparently not a JPEG");
}
}
public static MediaItem createMediaItemFromUri(Context context, Uri target, int mediaType) {
MediaItem item = null;
long id = ContentUris.parseId(target);
ContentResolver cr = context.getContentResolver();
String whereClause = Images.ImageColumns._ID + "=" + Long.toString(id);
try {
final Uri uri = (mediaType == MediaItem.MEDIA_TYPE_IMAGE)
? Images.Media.EXTERNAL_CONTENT_URI
: Video.Media.EXTERNAL_CONTENT_URI;
final String[] projection = (mediaType == MediaItem.MEDIA_TYPE_IMAGE)
? CacheService.PROJECTION_IMAGES
: CacheService.PROJECTION_VIDEOS;
Cursor cursor = cr.query(uri, projection, whereClause, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
item = new MediaItem();
CacheService.populateMediaItemFromCursor(item, cr, cursor, uri.toString() + "/");
}
cursor.close();
cursor = null;
}
} catch (Exception e) {
// If the database operation failed for any reason.
;
}
item.mId = id;
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 = Utils.getBucketIdFromUri(context.getContentResolver(), Uri.parse(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;
}
public String[] getDatabaseUris() {
return new String[] {Images.Media.EXTERNAL_CONTENT_URI.toString(), Video.Media.EXTERNAL_CONTENT_URI.toString()};
}
public void refresh(final MediaFeed feed, final String[] databaseUris) {
// We check to see what has changed.
long[] ids = CacheService.computeDirtySets(mContext);
int numDirtySets = ids.length;
for (int i = 0; i < numDirtySets; ++i) {
long setId = ids[i];
if (feed.getMediaSet(setId) != null) {
MediaSet newSet = feed.replaceMediaSet(setId, this);
newSet.generateTitle(true);
} else {
MediaSet mediaSet = feed.addMediaSet(setId, this);
if (setId == CAMERA_BUCKET_ID) {
mediaSet.mName = CAMERA_STRING;
} else if (setId == DOWNLOAD_BUCKET_ID) {
mediaSet.mName = DOWNLOAD_STRING;
}
mediaSet.generateTitle(true);
}
}
}
}