Home | History | Annotate | Download | only in media
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.cooliris.media;
     18 
     19 import java.io.File;
     20 import java.io.IOException;
     21 import java.net.URI;
     22 import java.util.ArrayList;
     23 import java.util.List;
     24 
     25 import android.content.ContentResolver;
     26 import android.content.ContentUris;
     27 import android.content.ContentValues;
     28 import android.content.Context;
     29 import android.database.Cursor;
     30 import android.media.ExifInterface;
     31 import android.net.Uri;
     32 import android.os.Environment;
     33 import android.provider.MediaStore;
     34 import android.provider.MediaStore.Images;
     35 import android.provider.MediaStore.Video;
     36 import android.util.Log;
     37 
     38 import com.cooliris.cache.CacheService;
     39 
     40 public class LocalDataSource implements DataSource {
     41     private static final String TAG = "LocalDataSource";
     42     public static final String URI_ALL_MEDIA = MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString();
     43     public static final DiskCache sThumbnailCache = new DiskCache("local-image-thumbs");
     44     public static final DiskCache sThumbnailCacheVideo = new DiskCache("local-video-thumbs");
     45 
     46     public static final String CAMERA_STRING = "Camera";
     47     public static final String DOWNLOAD_STRING = "download";
     48     public static final String CAMERA_BUCKET_NAME = Environment.getExternalStorageDirectory().toString() + "/DCIM/" + CAMERA_STRING;
     49     public static final String DOWNLOAD_BUCKET_NAME = Environment.getExternalStorageDirectory().toString() + "/" + DOWNLOAD_STRING;
     50     public static final int CAMERA_BUCKET_ID = getBucketId(CAMERA_BUCKET_NAME);
     51     public static final int DOWNLOAD_BUCKET_ID = getBucketId(DOWNLOAD_BUCKET_NAME);
     52 
     53     /**
     54      * Matches code in MediaProvider.computeBucketValues. Should be a common
     55      * function.
     56      */
     57     public static int getBucketId(String path) {
     58         return (path.toLowerCase().hashCode());
     59     }
     60 
     61     private final String mUri;
     62     private final String mBucketId;
     63     private boolean mDone;;
     64     private final boolean mSingleUri;
     65     private final boolean mAllItems;
     66     private final boolean mFlattenAllItems;
     67     private final DiskCache mDiskCache;
     68     private boolean mIncludeImages;
     69     private boolean mIncludeVideos;
     70     private Context mContext;
     71 
     72     public LocalDataSource(final Context context, final String uri, final boolean flattenAllItems) {
     73         this.mUri = uri;
     74         mContext = context;
     75         mIncludeImages = true;
     76         mIncludeVideos = false;
     77         String bucketId = Uri.parse(uri).getQueryParameter("bucketId");
     78         if (bucketId != null && bucketId.length() > 0) {
     79             mBucketId = bucketId;
     80         } else {
     81             mBucketId = null;
     82         }
     83         mFlattenAllItems = flattenAllItems;
     84         if (mBucketId == null) {
     85             if (uri.equals(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString())) {
     86                 mAllItems = true;
     87             } else {
     88                 mAllItems = false;
     89             }
     90         } else {
     91             mAllItems = false;
     92         }
     93         mSingleUri = isSingleImageMode(uri) && mBucketId == null;
     94         mDone = false;
     95         mDiskCache = mUri.startsWith(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString())
     96                 || mUri.startsWith(MediaStore.Video.Media.EXTERNAL_CONTENT_URI.toString())
     97                 || mUri.startsWith("file://") ? sThumbnailCache
     98                 : null;
     99     }
    100 
    101     public void setMimeFilter(boolean includeImages, boolean includeVideos) {
    102         mIncludeImages = includeImages;
    103         mIncludeVideos = includeVideos;
    104     }
    105 
    106     public void shutdown() {
    107 
    108     }
    109 
    110     public boolean isSingleImage() {
    111         return mSingleUri;
    112     }
    113 
    114     private static boolean isSingleImageMode(String uriString) {
    115         return !uriString.equals(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString())
    116                 && !uriString.equals(MediaStore.Images.Media.INTERNAL_CONTENT_URI.toString());
    117     }
    118 
    119     public DiskCache getThumbnailCache() {
    120         return mDiskCache;
    121     }
    122 
    123     public void loadItemsForSet(MediaFeed feed, MediaSet parentSet, int rangeStart, int rangeEnd) {
    124         if (parentSet.mNumItemsLoaded > 0 && mDone) {
    125             return;
    126         }
    127         if (mSingleUri && !mDone) {
    128             MediaItem item = new MediaItem();
    129             item.mId = 0;
    130             item.mFilePath = "";
    131             item.setMediaType((isImage(mUri)) ? MediaItem.MEDIA_TYPE_IMAGE : MediaItem.MEDIA_TYPE_VIDEO);
    132             if (mUri.startsWith(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString())
    133                 || mUri.startsWith(MediaStore.Video.Media.EXTERNAL_CONTENT_URI.toString())) {
    134                 MediaItem newItem = createMediaItemFromUri(mContext, Uri.parse(mUri), item.getMediaType());
    135                 if (newItem != null) {
    136                     item = newItem;
    137                     String fileUri = new File(item.mFilePath).toURI().toString();
    138                     parentSet.mName = Utils.getBucketNameFromUri(mContext.getContentResolver(), Uri.parse(fileUri));
    139                     parentSet.mId = Utils.getBucketIdFromUri(mContext.getContentResolver(), Uri.parse(fileUri));
    140                     parentSet.generateTitle(true);
    141                 }
    142             } else if (mUri.startsWith("file://")) {
    143                 MediaItem newItem = null;
    144                 int numRetries = 15;
    145                 do {
    146                     newItem = createMediaItemFromFileUri(mContext, mUri);
    147                     if (newItem == null) {
    148                         --numRetries;
    149                         try {
    150                             Thread.sleep(500);
    151                         } catch (InterruptedException e) {
    152                             ;
    153                         }
    154                     }
    155                 } while (newItem == null && numRetries >= 0);
    156                 if (newItem != null) {
    157                     item = newItem;
    158                 } else {
    159                     item.mContentUri = mUri;
    160                     item.mThumbnailUri = mUri;
    161                     item.mScreennailUri = mUri;
    162                     feed.setSingleImageMode(true);
    163                 }
    164             } else {
    165                 item.mContentUri = mUri;
    166                 item.mThumbnailUri = mUri;
    167                 item.mScreennailUri = mUri;
    168                 feed.setSingleImageMode(true);
    169             }
    170             if (item != null) {
    171                 feed.addItemToMediaSet(item, parentSet);
    172                 // Parse EXIF orientation if a local file.
    173                 if (mUri.startsWith("file://")) {
    174                     try {
    175                         ExifInterface exif = new ExifInterface(Uri.parse(mUri).getPath());
    176                         item.mRotation = Shared.exifOrientationToDegrees(exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
    177                                 ExifInterface.ORIENTATION_NORMAL));
    178                     } catch (IOException e) {
    179                         Log.i(TAG, "Error reading Exif information, probably not a jpeg.");
    180                     }
    181                 }
    182                 // Try and get the date taken for this item.
    183                 long dateTaken = CacheService.fetchDateTaken(item);
    184                 if (dateTaken != -1L) {
    185                     item.mDateTakenInMs = dateTaken;
    186                 }
    187                 CacheService.loadMediaItemsIntoMediaFeed(mContext, feed, parentSet, rangeStart, rangeEnd, mIncludeImages, mIncludeVideos);
    188                 ArrayList<MediaItem> items = parentSet.getItems();
    189                 int numItems = items.size();
    190                 if (numItems == 1 && parentSet.mNumItemsLoaded > 1) {
    191                     parentSet.mNumItemsLoaded = 1;
    192                 }
    193                 parentSet.removeDuplicate(item);
    194             }
    195             parentSet.updateNumExpectedItems();
    196             parentSet.generateTitle(true);
    197         } else if (mUri.equals(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString()) & mFlattenAllItems) {
    198             final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
    199             final ContentResolver cr = mContext.getContentResolver();
    200             String where = null;
    201             try {
    202                 Cursor cursor = cr.query(uriImages, CacheService.PROJECTION_IMAGES, where, null, null);
    203                 if (cursor != null && cursor.moveToFirst()) {
    204                     parentSet.setNumExpectedItems(cursor.getCount());
    205                     do {
    206                         if (Thread.interrupted()) {
    207                             return;
    208                         }
    209                         final MediaItem item = new MediaItem();
    210                         CacheService.populateMediaItemFromCursor(item, cr, cursor, CacheService.BASE_CONTENT_STRING_IMAGES);
    211                         feed.addItemToMediaSet(item, parentSet);
    212                     } while (cursor.moveToNext());
    213                     if (cursor != null) {
    214                         cursor.close();
    215                         cursor = null;
    216                     }
    217                     parentSet.updateNumExpectedItems();
    218                     parentSet.generateTitle(true);
    219                 }
    220             } catch (Exception e) {
    221                 // If the database operation failed for any reason.
    222                 ;
    223             }
    224         } else {
    225             CacheService.loadMediaItemsIntoMediaFeed(mContext, feed, parentSet, rangeStart, rangeEnd, mIncludeImages, mIncludeVideos);
    226         }
    227         mDone = true;
    228     }
    229 
    230     private static boolean isImage(String uriString) {
    231         return !uriString.startsWith(MediaStore.Video.Media.EXTERNAL_CONTENT_URI.toString());
    232     }
    233 
    234     public void loadMediaSets(final MediaFeed feed) {
    235         MediaSet set = null; // Dummy set.
    236         boolean loadOtherSets = true;
    237         if (mSingleUri) {
    238             String name = Utils.getBucketNameFromUri(mContext.getContentResolver(), Uri.parse(mUri));
    239             long id = Utils.getBucketIdFromUri(mContext.getContentResolver(), Uri.parse(mUri));
    240             set = feed.addMediaSet(id, this);
    241             set.mName = name;
    242             set.mId = id;
    243             set.setNumExpectedItems(2);
    244             set.generateTitle(true);
    245             set.mPicasaAlbumId = Shared.INVALID;
    246             if (this.getThumbnailCache() != sThumbnailCache) {
    247                 loadOtherSets = false;
    248             }
    249         } else if (mBucketId == null) {
    250             // All the buckets.
    251             if (mFlattenAllItems) {
    252                 set = feed.addMediaSet(0, this); // Create dummy set.
    253                 set.mName = Utils.getBucketNameFromUri(mContext.getContentResolver(), Uri.parse(mUri));
    254                 set.mId = getBucketId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString() + "/" + set.mName);
    255                 set.setNumExpectedItems(1);
    256                 set.generateTitle(true);
    257                 set.mPicasaAlbumId = Shared.INVALID;
    258             } else {
    259                 CacheService.loadMediaSets(mContext, feed, this, mIncludeImages, mIncludeVideos, true);
    260             }
    261         } else {
    262             CacheService.loadMediaSet(mContext, feed, this, Long.parseLong(mBucketId));
    263             ArrayList<MediaSet> sets = feed.getMediaSets();
    264             if (sets.size() > 0)
    265                 set = sets.get(0);
    266         }
    267         // We also load the other MediaSets
    268         if (!mAllItems && set != null && loadOtherSets) {
    269             final long setId = set.mId;
    270             if (!CacheService.isPresentInCache(setId)) {
    271                 CacheService.markDirty();
    272             }
    273             CacheService.loadMediaSets(mContext, feed, this, mIncludeImages, mIncludeVideos, false);
    274 
    275             // not re-ordering media sets in the case of displaying a single image
    276             if (!mSingleUri) {
    277                 feed.moveSetToFront(set);
    278             }
    279         }
    280     }
    281 
    282     public boolean performOperation(int operation, ArrayList<MediaBucket> mediaBuckets, Object data) {
    283         int numBuckets = mediaBuckets.size();
    284         ContentResolver cr = mContext.getContentResolver();
    285         switch (operation) {
    286         case MediaFeed.OPERATION_DELETE:
    287             for (int i = 0; i < numBuckets; ++i) {
    288                 MediaBucket bucket = mediaBuckets.get(i);
    289                 MediaSet set = bucket.mediaSet;
    290                 ArrayList<MediaItem> items = bucket.mediaItems;
    291                 if (set != null && items == null) {
    292                     // TODO bulk delete
    293                     // remove the entire bucket
    294                     final Uri uriImages = Images.Media.EXTERNAL_CONTENT_URI;
    295                     final Uri uriVideos = Video.Media.EXTERNAL_CONTENT_URI;
    296                     final String whereImages = Images.ImageColumns.BUCKET_ID + "=" + Long.toString(set.mId);
    297                     final String whereVideos = Video.VideoColumns.BUCKET_ID + "=" + Long.toString(set.mId);
    298                     cr.delete(uriImages, whereImages, null);
    299                     cr.delete(uriVideos, whereVideos, null);
    300                     //CacheService.markDirty();
    301                 }
    302                 if (set != null && items != null) {
    303                     // We need to remove these items from the set.
    304                     int numItems = items.size();
    305                     try {
    306                         for (int j = 0; j < numItems; ++j) {
    307                             MediaItem item = items.get(j);
    308                             cr.delete(Uri.parse(item.mContentUri), null, null);
    309                         }
    310                     } catch (Exception e) {
    311                         // If the database operation failed for any reason.
    312                         ;
    313                     }
    314                     set.updateNumExpectedItems();
    315                     set.generateTitle(true);
    316                     //CacheService.markDirty(set.mId);
    317                 }
    318             }
    319             break;
    320         case MediaFeed.OPERATION_ROTATE:
    321             for (int i = 0; i < numBuckets; ++i) {
    322                 MediaBucket bucket = mediaBuckets.get(i);
    323                 ArrayList<MediaItem> items = bucket.mediaItems;
    324                 if (items == null) {
    325                     continue;
    326                 }
    327                 float angleToRotate = ((Float) data).floatValue();
    328                 if (angleToRotate == 0) {
    329                     return true;
    330                 }
    331                 int numItems = items.size();
    332                 for (int j = 0; j < numItems; ++j) {
    333                     rotateItem(items.get(j), angleToRotate);
    334                 }
    335             }
    336             break;
    337         }
    338         return true;
    339     }
    340 
    341     private void rotateItem(final MediaItem item, float angleToRotate) {
    342         ContentResolver cr = mContext.getContentResolver();
    343         try {
    344             int currentOrientation = (int) item.mRotation;
    345             angleToRotate += currentOrientation;
    346             float rotation = Shared.normalizePositive(angleToRotate);
    347             String rotationString = Integer.toString((int) rotation);
    348 
    349             // Update the database entry.
    350             ContentValues values = new ContentValues();
    351             values.put(Images.ImageColumns.ORIENTATION, rotationString);
    352             try {
    353                 cr.update(Uri.parse(item.mContentUri), values, null, null);
    354             } catch (Exception e) {
    355                 // If the database operation fails for any reason.
    356                 ;
    357             }
    358 
    359             // Update the file EXIF information.
    360             Uri uri = Uri.parse(item.mContentUri);
    361             String uriScheme = uri.getScheme();
    362             if (uriScheme.equals("file") || uriScheme.equals("content")) {
    363                 final String path = (uriScheme.equals("file")) ? uri.getPath() : item.mFilePath;
    364                 ExifInterface exif = new ExifInterface(path);
    365                 exif.setAttribute(ExifInterface.TAG_ORIENTATION, Integer.toString(Shared.degreesToExifOrientation(rotation)));
    366                 exif.saveAttributes();
    367             }
    368 
    369             // Invalidate the cache entry.
    370             CacheService.markDirty(item.mParentMediaSet.mId);
    371 
    372             // Update the object representation of the item.
    373             item.mRotation = rotation;
    374         } catch (Exception e) {
    375             // System.out.println("Apparently not a JPEG");
    376         }
    377     }
    378 
    379     public static MediaItem createMediaItemFromUri(Context context, Uri target, int mediaType) {
    380         MediaItem item = null;
    381         long id = ContentUris.parseId(target);
    382         ContentResolver cr = context.getContentResolver();
    383         String whereClause = Images.ImageColumns._ID + "=" + Long.toString(id);
    384         try {
    385             final Uri uri = (mediaType == MediaItem.MEDIA_TYPE_IMAGE)
    386                     ? Images.Media.EXTERNAL_CONTENT_URI
    387                     : Video.Media.EXTERNAL_CONTENT_URI;
    388             final String[] projection = (mediaType == MediaItem.MEDIA_TYPE_IMAGE)
    389                     ? CacheService.PROJECTION_IMAGES
    390                     : CacheService.PROJECTION_VIDEOS;
    391             Cursor cursor = cr.query(uri, projection, whereClause, null, null);
    392             if (cursor != null) {
    393                 if (cursor.moveToFirst()) {
    394                     item = new MediaItem();
    395                     CacheService.populateMediaItemFromCursor(item, cr, cursor, uri.toString() + "/");
    396                 }
    397                 cursor.close();
    398                 cursor = null;
    399             }
    400         } catch (Exception e) {
    401             // If the database operation failed for any reason.
    402             ;
    403         }
    404         item.mId = id;
    405         return item;
    406     }
    407 
    408     public static MediaItem createMediaItemFromFileUri(Context context, String fileUri) {
    409         MediaItem item = null;
    410         String filepath = new File(URI.create(fileUri)).toString();
    411         ContentResolver cr = context.getContentResolver();
    412         long bucketId = Utils.getBucketIdFromUri(context.getContentResolver(), Uri.parse(fileUri));
    413         String whereClause = Images.ImageColumns.BUCKET_ID + "=" + bucketId + " AND " + Images.ImageColumns.DATA + "='" + filepath
    414                 + "'";
    415         try {
    416             Cursor cursor = cr.query(Images.Media.EXTERNAL_CONTENT_URI, CacheService.PROJECTION_IMAGES, whereClause, null, null);
    417             if (cursor != null) {
    418                 if (cursor.moveToFirst()) {
    419                     item = new MediaItem();
    420                     CacheService.populateMediaItemFromCursor(item, cr, cursor, Images.Media.EXTERNAL_CONTENT_URI.toString() + "/");
    421                 }
    422                 cursor.close();
    423                 cursor = null;
    424             }
    425         } catch (Exception e) {
    426             // If the database operation failed for any reason.
    427             ;
    428         }
    429         return item;
    430     }
    431 
    432     public String[] getDatabaseUris() {
    433         return new String[] {Images.Media.EXTERNAL_CONTENT_URI.toString(), Video.Media.EXTERNAL_CONTENT_URI.toString()};
    434     }
    435 
    436     public void refresh(final MediaFeed feed, final String[] databaseUris) {
    437         // We check to see what has changed.
    438         long[] ids = CacheService.computeDirtySets(mContext);
    439         int numDirtySets = ids.length;
    440         for (int i = 0; i < numDirtySets; ++i) {
    441             long setId = ids[i];
    442             if (feed.getMediaSet(setId) != null) {
    443                 MediaSet newSet = feed.replaceMediaSet(setId, this);
    444                 newSet.generateTitle(true);
    445             } else {
    446                 MediaSet mediaSet = feed.addMediaSet(setId, this);
    447                 if (setId == CAMERA_BUCKET_ID) {
    448                     mediaSet.mName = CAMERA_STRING;
    449                 } else if (setId == DOWNLOAD_BUCKET_ID) {
    450                     mediaSet.mName = DOWNLOAD_STRING;
    451                 }
    452                 mediaSet.generateTitle(true);
    453             }
    454         }
    455     }
    456 
    457 }
    458