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