1 package com.cooliris.media;
4 import java.io.IOException;
5 import java.util.ArrayList;
8 import android.content.ContentResolver;
9 import android.content.ContentUris;
10 import android.content.Context;
11 import android.database.Cursor;
12 import android.media.ExifInterface;
13 import android.net.Uri;
14 import android.os.Environment;
15 import android.provider.MediaStore;
16 import android.provider.MediaStore.Images;
17 import android.provider.MediaStore.Video;
18 import android.util.Log;
20 import com.cooliris.cache.CacheService;
22 // TODO: Merge SingleDataSource and LocalDataSource into one type if possible
24 public class SingleDataSource implements DataSource {
25 private static final String TAG = "SingleDataSource";
26 public final String mUri;
27 public final String mBucketId;
29 public final boolean mSlideshow;
30 public final boolean mSingleUri;
31 public final boolean mAllItems;
32 public final DiskCache mDiskCache;
33 private Context mContext;
35 public SingleDataSource(final Context context, final String uri, final boolean slideshow) {
38 String bucketId = Uri.parse(uri).getQueryParameter("bucketId");
39 if (bucketId != null && bucketId.length() > 0) {
44 if (mBucketId == null) {
45 if (uri.equals(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString())) {
53 this.mSlideshow = slideshow;
54 mSingleUri = isSingleImageMode(uri) && mBucketId == null;
56 mDiskCache = mUri.startsWith(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString()) || mUri.startsWith("file://") ? LocalDataSource.sThumbnailCache
60 public void shutdown() {
64 public boolean isSingleImage() {
68 private static boolean isSingleImageMode(String uriString) {
69 return !uriString.equals(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString())
70 && !uriString.equals(MediaStore.Images.Media.INTERNAL_CONTENT_URI.toString());
73 public DiskCache getThumbnailCache() {
77 public void loadItemsForSet(MediaFeed feed, MediaSet parentSet, int rangeStart, int rangeEnd) {
78 if (parentSet.mNumItemsLoaded > 0 && mDone) {
81 if (mSingleUri && !mDone) {
82 MediaItem item = new MediaItem();
85 item.setMediaType((isImage(mUri)) ? MediaItem.MEDIA_TYPE_IMAGE : MediaItem.MEDIA_TYPE_VIDEO);
86 if (mUri.startsWith(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString())) {
87 MediaItem newItem = LocalDataSource.createMediaItemFromUri(mContext, Uri.parse(mUri));
88 if (newItem != null) {
90 String fileUri = new File(item.mFilePath).toURI().toString();
91 parentSet.mName = Utils.getBucketNameFromUri(Uri.parse(fileUri));
92 parentSet.mId = parseBucketIdFromFileUri(fileUri);
93 parentSet.generateTitle(true);
95 } else if (mUri.startsWith("file://")) {
96 MediaItem newItem = null;
99 newItem = LocalDataSource.createMediaItemFromFileUri(mContext, mUri);
100 if (newItem == null) {
104 } catch (InterruptedException e) {
108 } while (newItem == null && numRetries >= 0);
109 if (newItem != null) {
112 item.mContentUri = mUri;
113 item.mThumbnailUri = mUri;
114 item.mScreennailUri = mUri;
115 feed.setSingleImageMode(true);
118 item.mContentUri = mUri;
119 item.mThumbnailUri = mUri;
120 item.mScreennailUri = mUri;
121 feed.setSingleImageMode(true);
124 feed.addItemToMediaSet(item, parentSet);
125 // Parse EXIF orientation if a local file.
126 if (mUri.startsWith("file://")) {
128 ExifInterface exif = new ExifInterface(Uri.parse(mUri).getPath());
129 item.mRotation = Shared.exifOrientationToDegrees(exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
130 ExifInterface.ORIENTATION_NORMAL));
131 } catch (IOException e) {
132 Log.i(TAG, "Error reading Exif information, probably not a jpeg.");
135 // Try and get the date taken for this item.
136 long dateTaken = CacheService.fetchDateTaken(item);
137 if (dateTaken != -1L) {
138 item.mDateTakenInMs = dateTaken;
140 CacheService.loadMediaItemsIntoMediaFeed(feed, parentSet, rangeStart, rangeEnd, true, false);
141 ArrayList<MediaItem> items = parentSet.getItems();
142 int numItems = items.size();
143 for (int i = 1; i < numItems; ++i) {
144 MediaItem thisItem = items.get(i);
145 String filePath = Uri.fromFile(new File(thisItem.mFilePath)).toString();
146 if (item.mId == thisItem.mId || ((item.mContentUri != null && thisItem.mContentUri != null) && (item.mContentUri.equals(thisItem.mContentUri)
147 || item.mContentUri.equals(filePath)))) {
148 items.remove(thisItem);
149 --parentSet.mNumItemsLoaded;
154 parentSet.updateNumExpectedItems();
155 parentSet.generateTitle(true);
156 } else if (mUri.equals(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString())) {
157 final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
158 final ContentResolver cr = mContext.getContentResolver();
160 Cursor cursor = cr.query(uriImages, CacheService.PROJECTION_IMAGES, where, null, null);
161 if (cursor != null && cursor.moveToFirst()) {
162 parentSet.setNumExpectedItems(cursor.getCount());
164 if (Thread.interrupted()) {
167 final MediaItem item = new MediaItem();
168 CacheService.populateMediaItemFromCursor(item, cr, cursor, CacheService.BASE_CONTENT_STRING_IMAGES);
169 feed.addItemToMediaSet(item, parentSet);
170 } while (cursor.moveToNext());
171 if (cursor != null) {
175 parentSet.updateNumExpectedItems();
176 parentSet.generateTitle(true);
179 CacheService.loadMediaItemsIntoMediaFeed(feed, parentSet, rangeStart, rangeEnd, true, true);
184 public static long parseBucketIdFromFileUri(String uriString) {
185 // This is a local folder.
186 final Uri uri = Uri.parse(uriString);
187 final List<String> paths = uri.getPathSegments();
188 final int numPaths = paths.size() - 1;
189 StringBuffer pathBuilder = new StringBuffer(Environment.getExternalStorageDirectory().toString());
191 pathBuilder.append("/");
192 for (int i = 0; i < numPaths; ++i) {
193 String path = paths.get(i);
194 if (!"file".equals(path) && !"sdcard".equals(path)) {
195 pathBuilder.append(path);
196 if (i != numPaths - 1) {
197 pathBuilder.append("/");
201 return LocalDataSource.getBucketId(pathBuilder.toString());
204 private static boolean isImage(String uriString) {
205 return !uriString.startsWith(MediaStore.Video.Media.EXTERNAL_CONTENT_URI.toString());
208 private static long parseIdFromContentUri(String uri) {
210 long id = ContentUris.parseId(Uri.parse(uri));
212 } catch (Exception e) {
217 public void loadMediaSets(MediaFeed feed) {
218 MediaSet set = null; // Dummy set.
219 boolean loadOtherSets = true;
221 String name = Utils.getBucketNameFromUri(Uri.parse(mUri));
222 long id = getBucketId(mUri);
223 set = feed.addMediaSet(id, this);
225 set.setNumExpectedItems(2);
226 set.generateTitle(true);
227 set.mPicasaAlbumId = Shared.INVALID;
228 if (this.getThumbnailCache() != LocalDataSource.sThumbnailCache) {
229 loadOtherSets = false;
231 } else if (mBucketId == null) {
233 set = feed.addMediaSet(0, this); // Create dummy set.
234 set.mName = Utils.getBucketNameFromUri(Uri.parse(mUri));
235 set.mId = LocalDataSource.getBucketId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString() + "/" + set.mName);
236 set.setNumExpectedItems(1);
237 set.generateTitle(true);
238 set.mPicasaAlbumId = Shared.INVALID;
240 CacheService.loadMediaSet(feed, this, Long.parseLong(mBucketId));
241 ArrayList<MediaSet> sets = feed.getMediaSets();
245 // We also load the other MediaSets
246 if (!mAllItems && set != null && loadOtherSets) {
247 if (!CacheService.isPresentInCache(set.mId)) {
248 CacheService.markDirty(mContext);
250 CacheService.loadMediaSets(feed, this, true, false);
254 private long getBucketId(String uriString) {
255 if (uriString.startsWith("content://.")) {
256 return parseIdFromContentUri(uriString);
258 return parseBucketIdFromFileUri(uriString);
262 public boolean performOperation(int operation, ArrayList<MediaBucket> mediaBuckets, Object data) {
263 int numBuckets = mediaBuckets.size();
264 ContentResolver cr = mContext.getContentResolver();
266 case MediaFeed.OPERATION_DELETE:
267 // TODO: Refactor this against LocalDataSource.performOperation.
268 for (int i = 0; i < numBuckets; ++i) {
269 MediaBucket bucket = mediaBuckets.get(i);
270 MediaSet set = bucket.mediaSet;
271 ArrayList<MediaItem> items = bucket.mediaItems;
272 if (set != null && items == null) {
274 // remove the entire bucket
275 final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
276 final Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI;
277 final String whereImages = Images.ImageColumns.BUCKET_ID + "=" + Long.toString(set.mId);
278 final String whereVideos = Video.VideoColumns.BUCKET_ID + "=" + Long.toString(set.mId);
279 cr.delete(uriImages, whereImages, null);
280 cr.delete(uriVideos, whereVideos, null);
281 CacheService.markDirty(mContext);
283 if (set != null && items != null) {
284 // We need to remove these items from the set.
285 int numItems = items.size();
286 for (int j = 0; j < numItems; ++j) {
287 MediaItem item = items.get(j);
288 cr.delete(Uri.parse(item.mContentUri), null, null);
290 set.updateNumExpectedItems();
291 set.generateTitle(true);
292 CacheService.markDirty(mContext, set.mId);
296 case MediaFeed.OPERATION_ROTATE:
297 for (int i = 0; i < numBuckets; ++i) {
298 MediaBucket bucket = mediaBuckets.get(i);
299 ArrayList<MediaItem> items = bucket.mediaItems;
303 float angleToRotate = ((Float) data).floatValue();
304 if (angleToRotate == 0) {
307 int numItems = items.size();
308 for (int j = 0; j < numItems; ++j) {
309 rotateItem(items.get(j), angleToRotate);
317 private void rotateItem(final MediaItem item, float angleToRotate) {
319 int currentOrientation = (int) item.mRotation;
320 angleToRotate += currentOrientation;
321 float rotation = Shared.normalizePositive(angleToRotate);
323 // Update the file EXIF information.
324 Uri uri = Uri.parse(item.mContentUri);
325 String uriScheme = uri.getScheme();
326 if (uriScheme.equals("file")) {
327 ExifInterface exif = new ExifInterface(uri.getPath());
328 exif.setAttribute(ExifInterface.TAG_ORIENTATION, Integer.toString(Shared.degreesToExifOrientation(rotation)));
329 exif.saveAttributes();
332 // Update the object representation of the item.
333 item.mRotation = rotation;
334 } catch (Exception e) {