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