1 package com.cooliris.media;
5 import java.util.ArrayList;
7 import android.content.ContentResolver;
8 import android.content.ContentUris;
9 import android.content.ContentValues;
10 import android.content.Context;
11 import android.database.ContentObserver;
12 import android.database.Cursor;
13 import android.media.ExifInterface;
14 import android.net.Uri;
15 import android.os.Environment;
16 import android.os.Handler;
17 import android.provider.MediaStore.Images;
18 import android.provider.MediaStore.Video;
19 import android.util.Log;
21 import com.cooliris.cache.CacheService;
23 public final class LocalDataSource implements DataSource {
24 private static final String TAG = "LocalDataSource";
26 public static final DiskCache sThumbnailCache = new DiskCache("local-image-thumbs");
27 public static final DiskCache sThumbnailCacheVideo = new DiskCache("local-video-thumbs");
29 public static final String CAMERA_STRING = "Camera";
30 public static final String DOWNLOAD_STRING = "download";
31 public static final String CAMERA_BUCKET_NAME = Environment.getExternalStorageDirectory().toString() + "/DCIM/" + CAMERA_STRING;
32 public static final String DOWNLOAD_BUCKET_NAME = Environment.getExternalStorageDirectory().toString() + "/" + DOWNLOAD_STRING;
33 public static final int CAMERA_BUCKET_ID = getBucketId(CAMERA_BUCKET_NAME);
34 public static final int DOWNLOAD_BUCKET_ID = getBucketId(DOWNLOAD_BUCKET_NAME);
36 public static boolean sObserverActive = false;
37 private boolean mDisableImages;
38 private boolean mDisableVideos;
41 * Matches code in MediaProvider.computeBucketValues. Should be a common
44 public static int getBucketId(String path) {
45 return (path.toLowerCase().hashCode());
48 private Context mContext;
49 private ContentObserver mObserver;
51 public LocalDataSource(Context context) {
55 public void setMimeFilter(boolean disableImages, boolean disableVideos) {
56 mDisableImages = disableImages;
57 mDisableVideos = disableVideos;
60 public void loadMediaSets(final MediaFeed feed) {
61 if (mContext == null) {
65 CacheService.loadMediaSets(feed, this, !mDisableImages, !mDisableVideos);
66 Handler handler = ((Gallery) mContext).getHandler();
67 ContentObserver observer = new ContentObserver(handler) {
68 public void onChange(boolean selfChange) {
69 final boolean isPaused = ((Gallery) mContext).isPaused();
71 MediaSet mediaSet = feed.getCurrentSet();
72 if (mediaSet != null) {
73 CacheService.markDirtyImmediate(mediaSet.mId);
74 refreshUI(feed, mediaSet.mId);
77 CacheService.senseDirty(mContext, new CacheService.Observer() {
78 public void onChange(long[] ids) {
82 int numLongs = ids.length;
83 for (int i = 0; i < numLongs; ++i) {
84 refreshUI(feed, ids[i]);
93 Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
94 Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI;
95 ContentResolver cr = mContext.getContentResolver();
97 cr.registerContentObserver(uriImages, true, observer);
98 cr.registerContentObserver(uriVideos, true, observer);
99 sObserverActive = true;
102 public void shutdown() {
106 private void stopListeners() {
107 ContentResolver cr = mContext.getContentResolver();
108 if (mObserver != null) {
109 cr.unregisterContentObserver(mObserver);
110 cr.unregisterContentObserver(mObserver);
112 sObserverActive = false;
115 protected void refreshUI(MediaFeed feed, long setIdToUse) {
116 if (setIdToUse == Shared.INVALID) {
119 if (feed.getMediaSet(setIdToUse) == null) {
120 MediaSet mediaSet = feed.addMediaSet(setIdToUse, this);
121 if (setIdToUse == CAMERA_BUCKET_ID) {
122 mediaSet.mName = CAMERA_STRING;
123 } else if (setIdToUse == DOWNLOAD_BUCKET_ID) {
124 mediaSet.mName = DOWNLOAD_STRING;
126 mediaSet.generateTitle(true);
128 MediaSet mediaSet = feed.replaceMediaSet(setIdToUse, this);
129 Log.i(TAG, "Replacing mediaset " + mediaSet.mName + " id " + setIdToUse + " current Id " + mediaSet.mId);
130 if (setIdToUse == CAMERA_BUCKET_ID) {
131 mediaSet.mName = CAMERA_STRING;
132 } else if (setIdToUse == DOWNLOAD_BUCKET_ID) {
133 mediaSet.mName = DOWNLOAD_STRING;
135 mediaSet.generateTitle(true);
139 public void loadItemsForSet(final MediaFeed feed, final MediaSet parentSet, int rangeStart, int rangeEnd) {
140 // Quick load from the cache.
141 if (mContext == null || parentSet == null) {
144 loadMediaItemsIntoMediaFeed(feed, parentSet, rangeStart, rangeEnd);
147 private void loadMediaItemsIntoMediaFeed(final MediaFeed mediaFeed, final MediaSet set, int rangeStart, int rangeEnd) {
148 if (rangeEnd - rangeStart < 0) {
151 CacheService.loadMediaItemsIntoMediaFeed(mediaFeed, set, rangeStart, rangeEnd, !mDisableImages, !mDisableVideos);
152 if (set.mId == CAMERA_BUCKET_ID) {
153 mediaFeed.moveSetToFront(set);
157 public boolean performOperation(final int operation, final ArrayList<MediaBucket> mediaBuckets, final Object data) {
158 int numBuckets = mediaBuckets.size();
159 ContentResolver cr = mContext.getContentResolver();
161 case MediaFeed.OPERATION_DELETE:
162 for (int i = 0; i < numBuckets; ++i) {
163 MediaBucket bucket = mediaBuckets.get(i);
164 MediaSet set = bucket.mediaSet;
165 ArrayList<MediaItem> items = bucket.mediaItems;
166 if (set != null && items == null) {
167 // Remove the entire bucket.
168 final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
169 final Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI;
170 final String whereImages = Images.ImageColumns.BUCKET_ID + "=" + Long.toString(set.mId);
171 final String whereVideos = Video.VideoColumns.BUCKET_ID + "=" + Long.toString(set.mId);
172 cr.delete(uriImages, whereImages, null);
173 cr.delete(uriVideos, whereVideos, null);
174 CacheService.markDirty(mContext);
176 if (set != null && items != null) {
177 // We need to remove these items from the set.
178 int numItems = items.size();
180 for (int j = 0; j < numItems; ++j) {
181 MediaItem item = items.get(j);
182 cr.delete(Uri.parse(item.mContentUri), null, null);
184 } catch (Exception e) {
185 // If the database operation failed for any reason.
188 set.updateNumExpectedItems();
189 set.generateTitle(true);
190 CacheService.markDirty(mContext, set.mId);
194 case MediaFeed.OPERATION_ROTATE:
195 for (int i = 0; i < numBuckets; ++i) {
196 MediaBucket bucket = mediaBuckets.get(i);
197 ArrayList<MediaItem> items = bucket.mediaItems;
201 float angleToRotate = ((Float) data).floatValue();
202 if (angleToRotate == 0) {
205 int numItems = items.size();
206 for (int j = 0; j < numItems; ++j) {
207 rotateItem(items.get(j), angleToRotate);
215 private void rotateItem(final MediaItem item, float angleToRotate) {
216 ContentResolver cr = mContext.getContentResolver();
218 int currentOrientation = (int) item.mRotation;
219 angleToRotate += currentOrientation;
220 float rotation = Shared.normalizePositive(angleToRotate);
221 String rotationString = Integer.toString((int) rotation);
223 // Update the database entry.
224 ContentValues values = new ContentValues();
225 values.put(Images.ImageColumns.ORIENTATION, rotationString);
227 cr.update(Uri.parse(item.mContentUri), values, null, null);
228 } catch (Exception e) {
229 // If the database operation fails for any reason.
233 // Update the file EXIF information.
234 Uri uri = Uri.parse(item.mContentUri);
235 String uriScheme = uri.getScheme();
236 if (uriScheme.equals("file")) {
237 ExifInterface exif = new ExifInterface(uri.getPath());
238 exif.setAttribute(ExifInterface.TAG_ORIENTATION, Integer.toString(Shared.degreesToExifOrientation(rotation)));
239 exif.saveAttributes();
242 // Invalidate the cache entry.
243 CacheService.markDirty(mContext, item.mParentMediaSet.mId);
245 // Update the object representation of the item.
246 item.mRotation = rotation;
247 } catch (Exception e) {
248 // System.out.println("Apparently not a JPEG");
252 public DiskCache getThumbnailCache() {
253 return sThumbnailCache;
256 public static MediaItem createMediaItemFromUri(Context context, Uri target) {
257 MediaItem item = null;
258 long id = ContentUris.parseId(target);
259 ContentResolver cr = context.getContentResolver();
260 String whereClause = Images.ImageColumns._ID + "=" + Long.toString(id);
262 Cursor cursor = cr.query(Images.Media.EXTERNAL_CONTENT_URI, CacheService.PROJECTION_IMAGES, whereClause, null, null);
263 if (cursor != null) {
264 if (cursor.moveToFirst()) {
265 item = new MediaItem();
266 CacheService.populateMediaItemFromCursor(item, cr, cursor, Images.Media.EXTERNAL_CONTENT_URI.toString() + "/");
271 } catch (Exception e) {
272 // If the database operation failed for any reason.
278 public static MediaItem createMediaItemFromFileUri(Context context, String fileUri) {
279 MediaItem item = null;
280 String filepath = new File(URI.create(fileUri)).toString();
281 ContentResolver cr = context.getContentResolver();
282 long bucketId = SingleDataSource.parseBucketIdFromFileUri(fileUri);
283 String whereClause = Images.ImageColumns.BUCKET_ID + "=" + bucketId + " AND " + Images.ImageColumns.DATA + "='" + filepath
286 Cursor cursor = cr.query(Images.Media.EXTERNAL_CONTENT_URI, CacheService.PROJECTION_IMAGES, whereClause, null, null);
287 if (cursor != null) {
288 if (cursor.moveToFirst()) {
289 item = new MediaItem();
290 CacheService.populateMediaItemFromCursor(item, cr, cursor, Images.Media.EXTERNAL_CONTENT_URI.toString() + "/");
295 } catch (Exception e) {
296 // If the database operation failed for any reason.