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