1 package com.cooliris.media;
3 import java.util.ArrayList;
4 import java.util.HashMap;
6 import android.accounts.Account;
7 import android.content.ContentProviderClient;
8 import android.content.ContentResolver;
9 import android.content.Context;
10 import android.database.ContentObserver;
11 import android.database.Cursor;
12 import android.net.Uri;
13 import android.os.Handler;
14 import android.os.RemoteException;
15 import android.util.Log;
17 import com.cooliris.picasa.AlbumEntry;
18 import com.cooliris.picasa.Entry;
19 import com.cooliris.picasa.EntrySchema;
20 import com.cooliris.picasa.PicasaApi;
21 import com.cooliris.picasa.PicasaContentProvider;
22 import com.cooliris.picasa.PicasaService;
24 public 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
30 private ContentProviderClient mProviderClient;
31 private final Context mContext;
32 private ContentObserver mAlbumObserver;
34 public PicasaDataSource(final Context context) {
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;
46 if (username.contains("@gmail.") || username.contains("@googlemail.")) {
47 // Strip the domain from GMail accounts for canonicalization. TODO: is there an official way?
48 username = username.substring(0, username.indexOf('@'));
50 accountsEnabled.put(username, new Boolean(isEnabled));
52 return accountsEnabled;
55 public void loadMediaSets(final MediaFeed feed) {
56 // We do this here and not in the constructor to speed application loading time since this method is called in a background thread
57 if (mProviderClient == null) {
58 mProviderClient = mContext.getContentResolver().acquireContentProviderClient(PicasaContentProvider.AUTHORITY);
60 // Force permission dialog to be displayed if necessary. TODO: remove this after signed by Google.
61 PicasaApi.getAccounts(mContext);
63 // Ensure that users are up to date. TODO: also listen for accounts changed broadcast.
64 PicasaService.requestSync(mContext, PicasaService.TYPE_USERS_ALBUMS, 0);
65 final Handler handler = ((Gallery) mContext).getHandler();
66 final ContentObserver albumObserver = new ContentObserver(handler) {
67 public void onChange(boolean selfChange) {
68 loadMediaSetsIntoFeed(feed, true);
71 mAlbumObserver = albumObserver;
72 loadMediaSetsIntoFeed(feed, true);
75 ContentResolver cr = mContext.getContentResolver();
76 cr.registerContentObserver(PicasaContentProvider.ALBUMS_URI, false, mAlbumObserver);
77 cr.registerContentObserver(PicasaContentProvider.PHOTOS_URI, false, mAlbumObserver);
80 public void shutdown() {
81 if (mAlbumObserver != null) {
82 ContentResolver cr = mContext.getContentResolver();
83 cr.unregisterContentObserver(mAlbumObserver);
87 public void loadItemsForSet(final MediaFeed feed, final MediaSet parentSet, int rangeStart, int rangeEnd) {
88 if (parentSet == null) {
91 // Return a list of items within an album.
92 addItemsToFeed(feed, parentSet, rangeStart, rangeEnd);
96 protected void loadMediaSetsIntoFeed(final MediaFeed feed, boolean sync) {
97 final HashMap<String, Boolean> accountsEnabled = getAccountStatus(mContext);
98 final ContentProviderClient client = mProviderClient;
102 final EntrySchema albumSchema = AlbumEntry.SCHEMA;
103 final Cursor cursor = client.query(PicasaContentProvider.ALBUMS_URI, albumSchema.getProjection(), null, null,
104 DEFAULT_BUCKET_SORT_ORDER);
105 final AlbumEntry album = new AlbumEntry();
107 if (cursor.moveToFirst()) {
108 final int numAlbums = cursor.getCount();
109 final ArrayList<MediaSet> picasaSets = new ArrayList<MediaSet>(numAlbums);
111 albumSchema.cursorToObject(cursor, album);
112 final Boolean accountEnabledObj = accountsEnabled.get(album.user);
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);
122 mediaSet.setNumExpectedItems(album.numPhotos);
124 mediaSet.mPicasaAlbumId = album.id;
125 mediaSet.mSyncPending = album.photosDirty;
126 picasaSets.add(mediaSet);
128 } while (cursor.moveToNext());
131 } catch (RemoteException e) {
132 Log.e(TAG, "Error occurred loading albums");
136 private void addItemsToFeed(MediaFeed feed, MediaSet set, int start, int end) {
137 final ContentProviderClient client = mProviderClient;
138 Cursor cursor = null;
140 // Query photos in the album.
141 final EntrySchema photosSchema = PhotoProjection.SCHEMA;
142 final String whereInAlbum = "album_id = " + Long.toString(set.mId);
143 cursor = client.query(PicasaContentProvider.PHOTOS_URI, photosSchema.getProjection(), whereInAlbum, null, null);
144 final PhotoProjection photo = new PhotoProjection();
145 int count = cursor.getCount();
149 set.setNumExpectedItems(count);
150 set.generateTitle(true);
151 // Move to the next unread item.
152 final int newIndex = start + 1;
153 if (newIndex > count || !cursor.move(newIndex)) {
156 set.updateNumExpectedItems();
157 set.generateTitle(true);
160 if (set.mNumItemsLoaded == 0) {
161 photosSchema.cursorToObject(cursor, photo);
162 set.mMinTimestamp = photo.dateTaken;
164 photosSchema.cursorToObject(cursor, photo);
165 set.mMinTimestamp = photo.dateTaken;
166 cursor.moveToFirst();
168 for (int i = 0; i < end; ++i) {
169 photosSchema.cursorToObject(cursor, photo);
170 final MediaItem item = new MediaItem();
172 item.mEditUri = photo.editUri;
173 item.mMimeType = photo.contentType;
174 item.mDateTakenInMs = photo.dateTaken;
175 item.mLatitude = photo.latitude;
176 item.mLongitude = photo.longitude;
177 item.mThumbnailUri = photo.thumbnailUrl;
178 item.mScreennailUri = photo.screennailUrl;
179 item.mContentUri = photo.contentUrl;
180 item.mCaption = photo.title;
181 item.mWeblink = photo.htmlPageUrl;
182 item.mDescription = photo.summary;
183 item.mFilePath = item.mContentUri;
184 feed.addItemToMediaSet(item, set);
185 if (!cursor.moveToNext()) {
189 } catch (Exception e) {
190 Log.e(TAG, "Error occurred loading photos for album " + set.mId);
192 if (cursor != null) {
198 public boolean performOperation(final int operation, final ArrayList<MediaBucket> mediaBuckets, final Object data) {
200 if (operation == MediaFeed.OPERATION_DELETE) {
201 ContentProviderClient client = mProviderClient;
202 for (int i = 0, numBuckets = mediaBuckets.size(); i != numBuckets; ++i) {
203 MediaBucket bucket = mediaBuckets.get(i);
204 ArrayList<MediaItem> items = bucket.mediaItems;
207 String albumUri = PicasaContentProvider.ALBUMS_URI + "/" + bucket.mediaSet.mId;
208 client.delete(Uri.parse(albumUri), null, null);
210 // Delete a set of photos.
211 for (int j = 0, numItems = items.size(); j != numItems; ++j) {
212 MediaItem item = items.get(j);
214 String itemUri = PicasaContentProvider.PHOTOS_URI + "/" + item.mId;
215 client.delete(Uri.parse(itemUri), null, null);
222 } catch (RemoteException e) {
227 public DiskCache getThumbnailCache() {
228 return sThumbnailCache;
232 * The projection of PhotoEntry needed by the data source.
234 private static final class PhotoProjection extends Entry {
235 public static final EntrySchema SCHEMA = new EntrySchema(PhotoProjection.class);
237 public String editUri;
241 public String summary;
242 @Column("date_taken")
243 public long dateTaken;
245 public double latitude;
247 public double longitude;
248 @Column("thumbnail_url")
249 public String thumbnailUrl;
250 @Column("screennail_url")
251 public String screennailUrl;
252 @Column("content_url")
253 public String contentUrl;
254 @Column("content_type")
255 public String contentType;
256 @Column("html_page_url")
257 public String htmlPageUrl;