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.util.ArrayList;
     20 import java.util.Arrays;
     21 import java.util.HashMap;
     22 
     23 import android.accounts.Account;
     24 import android.content.ContentProviderClient;
     25 import android.content.ContentResolver;
     26 import android.content.Context;
     27 import android.database.ContentObserver;
     28 import android.database.Cursor;
     29 import android.net.Uri;
     30 import android.os.Handler;
     31 import android.os.RemoteException;
     32 import android.util.Log;
     33 
     34 import com.cooliris.app.App;
     35 import com.cooliris.picasa.AlbumEntry;
     36 import com.cooliris.picasa.Entry;
     37 import com.cooliris.picasa.EntrySchema;
     38 import com.cooliris.picasa.PhotoEntry;
     39 import com.cooliris.picasa.PicasaApi;
     40 import com.cooliris.picasa.PicasaContentProvider;
     41 import com.cooliris.picasa.PicasaService;
     42 
     43 public final class PicasaDataSource implements DataSource {
     44     private static final String TAG = "PicasaDataSource";
     45     public static final DiskCache sThumbnailCache = new DiskCache("picasa-thumbs");
     46     private static final String DEFAULT_BUCKET_SORT_ORDER = AlbumEntry.Columns.USER + ", " + AlbumEntry.Columns.DATE_PUBLISHED
     47             + " DESC";
     48 
     49     private ContentProviderClient mProviderClient;
     50     private final Context mContext;
     51 
     52     public PicasaDataSource(final Context context) {
     53         mContext = context;
     54     }
     55 
     56     public static final HashMap<String, Boolean> getAccountStatus(final Context context) {
     57         final Account[] accounts = PicasaApi.getAccounts(context);
     58         int numAccounts = accounts.length;
     59         HashMap<String, Boolean> accountsEnabled = new HashMap<String, Boolean>(numAccounts);
     60         for (int i = 0; i < numAccounts; ++i) {
     61             Account account = accounts[i];
     62             boolean isEnabled = ContentResolver.getSyncAutomatically(account, PicasaContentProvider.AUTHORITY);
     63             String username = PicasaApi.canonicalizeUsername(account.name);
     64             accountsEnabled.put(username, new Boolean(isEnabled));
     65         }
     66         return accountsEnabled;
     67     }
     68 
     69     public void loadMediaSets(final MediaFeed feed) {
     70         // We do this here and not in the constructor to speed application
     71         // loading time since this method is called in a background thread
     72         if (mProviderClient == null) {
     73             mProviderClient = mContext.getContentResolver().acquireContentProviderClient(PicasaContentProvider.AUTHORITY);
     74         }
     75 
     76         // Ensure that users are up to date. TODO: also listen for accounts
     77         // changed broadcast.
     78         PicasaService.requestSync(mContext, PicasaService.TYPE_USERS_ALBUMS, 0);
     79         loadMediaSetsIntoFeed(feed, true);
     80     }
     81 
     82     public void shutdown() {
     83         ;
     84     }
     85 
     86     public void loadItemsForSet(final MediaFeed feed, final MediaSet parentSet, int rangeStart, int rangeEnd) {
     87         if (parentSet == null) {
     88             return;
     89         } else {
     90             // Return a list of items within an album.
     91             addItemsToFeed(feed, parentSet, rangeStart, rangeEnd);
     92         }
     93     }
     94 
     95     protected void loadMediaSetsIntoFeed(final MediaFeed feed, boolean sync) {
     96         final HashMap<String, Boolean> accountsEnabled = getAccountStatus(mContext);
     97         final ContentProviderClient client = mProviderClient;
     98         if (client == null)
     99             return;
    100         try {
    101             final EntrySchema albumSchema = AlbumEntry.SCHEMA;
    102             final Cursor cursor = client.query(PicasaContentProvider.ALBUMS_URI, albumSchema.getProjection(), null, null,
    103                     DEFAULT_BUCKET_SORT_ORDER);
    104             final AlbumEntry album = new AlbumEntry();
    105             MediaSet mediaSet;
    106             if (cursor.moveToFirst()) {
    107                 final int numAlbums = cursor.getCount();
    108                 final ArrayList<MediaSet> picasaSets = new ArrayList<MediaSet>(numAlbums);
    109                 do {
    110                     albumSchema.cursorToObject(cursor, album);
    111                     String userLowerCase = album.syncAccount.toLowerCase();
    112                     final Boolean accountEnabledObj = accountsEnabled.get(userLowerCase);
    113                     final boolean accountEnabled = (accountEnabledObj == null) ? false : accountEnabledObj.booleanValue();
    114                     if (accountEnabled) {
    115                         mediaSet = feed.getMediaSet(album.id);
    116                         if (mediaSet == null) {
    117                             mediaSet = feed.addMediaSet(album.id, this);
    118                             mediaSet.mName = album.title;
    119                             mediaSet.mEditUri = album.editUri;
    120                             mediaSet.generateTitle(true);
    121                         } else {
    122                             mediaSet.setNumExpectedItems(album.numPhotos);
    123                         }
    124                         mediaSet.mPicasaAlbumId = album.id;
    125                         mediaSet.mIsLocal = false;
    126                         mediaSet.mSyncPending = album.photosDirty;
    127                         picasaSets.add(mediaSet);
    128                     }
    129                 } while (cursor.moveToNext());
    130             }
    131             cursor.close();
    132         } catch (RemoteException e) {
    133             Log.e(TAG, "Error occurred loading albums");
    134         }
    135     }
    136 
    137     private void addItemsToFeed(MediaFeed feed, MediaSet set, int start, int end) {
    138         final ContentProviderClient client = mProviderClient;
    139         Cursor cursor = null;
    140         try {
    141             // Query photos in the album.
    142             final EntrySchema photosSchema = PhotoProjection.SCHEMA;
    143             final String whereInAlbum = "album_id = " + Long.toString(set.mId);
    144             cursor = client.query(PicasaContentProvider.PHOTOS_URI, photosSchema.getProjection(), whereInAlbum, null, null);
    145             final PhotoProjection photo = new PhotoProjection();
    146             int count = cursor.getCount();
    147             if (count < end) {
    148                 end = count;
    149             }
    150             set.setNumExpectedItems(count);
    151             set.generateTitle(true);
    152             // Move to the next unread item.
    153             final int newIndex = start + 1;
    154             if (newIndex > count || !cursor.move(newIndex)) {
    155                 end = 0;
    156                 cursor.close();
    157                 set.updateNumExpectedItems();
    158                 set.generateTitle(true);
    159                 return;
    160             }
    161             if (set.mNumItemsLoaded == 0) {
    162                 photosSchema.cursorToObject(cursor, photo);
    163                 set.mMinTimestamp = photo.dateTaken;
    164                 cursor.moveToLast();
    165                 photosSchema.cursorToObject(cursor, photo);
    166                 set.mMinTimestamp = photo.dateTaken;
    167                 cursor.moveToFirst();
    168             }
    169             for (int i = 0; i < end; ++i) {
    170                 photosSchema.cursorToObject(cursor, photo);
    171                 final MediaItem item = new MediaItem();
    172                 item.mId = photo.id;
    173                 item.mEditUri = photo.editUri;
    174                 item.mMimeType = photo.contentType;
    175                 item.mDateTakenInMs = photo.dateTaken;
    176                 item.mLatitude = photo.latitude;
    177                 item.mLongitude = photo.longitude;
    178                 item.mThumbnailUri = photo.thumbnailUrl;
    179                 item.mScreennailUri = photo.screennailUrl;
    180                 item.mContentUri = photo.contentUrl;
    181                 item.mCaption = photo.title;
    182                 item.mWeblink = photo.htmlPageUrl;
    183                 item.mDescription = photo.summary;
    184                 item.mFilePath = item.mContentUri;
    185                 feed.addItemToMediaSet(item, set);
    186                 if (!cursor.moveToNext()) {
    187                     break;
    188                 }
    189             }
    190         } catch (Exception e) {
    191             Log.e(TAG, "Error occurred loading photos for album " + set.mId);
    192         } finally {
    193             if (cursor != null) {
    194                 cursor.close();
    195             }
    196         }
    197     }
    198 
    199     public boolean performOperation(final int operation, final ArrayList<MediaBucket> mediaBuckets, final Object data) {
    200         try {
    201             if (operation == MediaFeed.OPERATION_DELETE) {
    202                 ContentProviderClient client = mProviderClient;
    203                 for (int i = 0, numBuckets = mediaBuckets.size(); i != numBuckets; ++i) {
    204                     MediaBucket bucket = mediaBuckets.get(i);
    205                     ArrayList<MediaItem> items = bucket.mediaItems;
    206                     if (items == null) {
    207                         // Delete an album.
    208                         String albumUri = PicasaContentProvider.ALBUMS_URI + "/" + bucket.mediaSet.mId;
    209                         client.delete(Uri.parse(albumUri), null, null);
    210                     } else {
    211                         // Delete a set of photos.
    212                         for (int j = 0, numItems = items.size(); j != numItems; ++j) {
    213                             MediaItem item = items.get(j);
    214                             if (item != null) {
    215                                 String itemUri = PicasaContentProvider.PHOTOS_URI + "/" + item.mId;
    216                                 client.delete(Uri.parse(itemUri), null, null);
    217                             }
    218                         }
    219                     }
    220                 }
    221             }
    222             return true;
    223         } catch (RemoteException e) {
    224             return false;
    225         }
    226     }
    227 
    228     public DiskCache getThumbnailCache() {
    229         return sThumbnailCache;
    230     }
    231 
    232     /**
    233      * The projection of PhotoEntry needed by the data source.
    234      */
    235     private static final class PhotoProjection extends Entry {
    236         public static final EntrySchema SCHEMA = new EntrySchema(PhotoProjection.class);
    237         @Column("edit_uri")
    238         public String editUri;
    239         @Column("title")
    240         public String title;
    241         @Column("summary")
    242         public String summary;
    243         @Column("date_taken")
    244         public long dateTaken;
    245         @Column("latitude")
    246         public double latitude;
    247         @Column("longitude")
    248         public double longitude;
    249         @Column("thumbnail_url")
    250         public String thumbnailUrl;
    251         @Column("screennail_url")
    252         public String screennailUrl;
    253         @Column("content_url")
    254         public String contentUrl;
    255         @Column("content_type")
    256         public String contentType;
    257         @Column("html_page_url")
    258         public String htmlPageUrl;
    259     }
    260 
    261     public String[] getDatabaseUris() {
    262         return new String[] { PicasaContentProvider.ALBUMS_URI.toString(), PicasaContentProvider.PHOTOS_URI.toString()};
    263     }
    264 
    265     public void refresh(final MediaFeed feed, final String[] databaseUris) {
    266         // Depending on what URI changed, we either need to update the mediasets or the mediaitems of a set.
    267         if (databaseUris != null && databaseUris.length > 0) {
    268             if (ArrayUtils.contains(databaseUris, PicasaContentProvider.ALBUMS_URI.toString())) {
    269                 // We need to refresh all mediasets of this datasource type.
    270                 final ArrayList<MediaSet> mediaSets = feed.getMediaSets();
    271                 final int numMediaSets = mediaSets.size();
    272                 for (int i = 0; i < numMediaSets; ++i) {
    273                     final MediaSet set = mediaSets.get(i);
    274                     if (set.mDataSource instanceof PicasaDataSource) {
    275                         set.mDataSource = this;
    276                         set.refresh();
    277                     }
    278                 }
    279             } else if (ArrayUtils.contains(databaseUris, PicasaContentProvider.PHOTOS_URI.toString())) {
    280                 // We need to update just the one set that has these photos.
    281                 // This operation is not yet supported, so we might as well refresh everything.
    282                 final ArrayList<MediaSet> mediaSets = feed.getMediaSets();
    283                 final int numMediaSets = mediaSets.size();
    284                 for (int i = 0; i < numMediaSets; ++i) {
    285                     final MediaSet set = mediaSets.get(i);
    286                     if (set.mDataSource instanceof PicasaDataSource) {
    287                         set.mDataSource = this;
    288                         set.refresh();
    289                     }
    290                 }
    291             }
    292         }
    293     }
    294 }
    295