/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.cooliris.media; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import android.accounts.Account; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; import android.os.Handler; import android.os.RemoteException; import android.util.Log; import com.cooliris.app.App; import com.cooliris.picasa.AlbumEntry; import com.cooliris.picasa.Entry; import com.cooliris.picasa.EntrySchema; import com.cooliris.picasa.PhotoEntry; import com.cooliris.picasa.PicasaApi; import com.cooliris.picasa.PicasaContentProvider; import com.cooliris.picasa.PicasaService; public final class PicasaDataSource implements DataSource { private static final String TAG = "PicasaDataSource"; public static final DiskCache sThumbnailCache = new DiskCache("picasa-thumbs"); private static final String DEFAULT_BUCKET_SORT_ORDER = AlbumEntry.Columns.USER + ", " + AlbumEntry.Columns.DATE_PUBLISHED + " DESC"; private ContentProviderClient mProviderClient; private final Context mContext; public PicasaDataSource(final Context context) { mContext = context; } public static final HashMap<String, Boolean> getAccountStatus(final Context context) { final Account[] accounts = PicasaApi.getAccounts(context); int numAccounts = accounts.length; HashMap<String, Boolean> accountsEnabled = new HashMap<String, Boolean>(numAccounts); for (int i = 0; i < numAccounts; ++i) { Account account = accounts[i]; boolean isEnabled = ContentResolver.getSyncAutomatically(account, PicasaContentProvider.AUTHORITY); String username = PicasaApi.canonicalizeUsername(account.name); accountsEnabled.put(username, new Boolean(isEnabled)); } return accountsEnabled; } public void loadMediaSets(final MediaFeed feed) { // We do this here and not in the constructor to speed application // loading time since this method is called in a background thread if (mProviderClient == null) { mProviderClient = mContext.getContentResolver().acquireContentProviderClient(PicasaContentProvider.AUTHORITY); } // Ensure that users are up to date. TODO: also listen for accounts // changed broadcast. PicasaService.requestSync(mContext, PicasaService.TYPE_USERS_ALBUMS, 0); loadMediaSetsIntoFeed(feed, true); } public void shutdown() { ; } public void loadItemsForSet(final MediaFeed feed, final MediaSet parentSet, int rangeStart, int rangeEnd) { if (parentSet == null) { return; } else { // Return a list of items within an album. addItemsToFeed(feed, parentSet, rangeStart, rangeEnd); } } protected void loadMediaSetsIntoFeed(final MediaFeed feed, boolean sync) { final HashMap<String, Boolean> accountsEnabled = getAccountStatus(mContext); final ContentProviderClient client = mProviderClient; if (client == null) return; try { final EntrySchema albumSchema = AlbumEntry.SCHEMA; final Cursor cursor = client.query(PicasaContentProvider.ALBUMS_URI, albumSchema.getProjection(), null, null, DEFAULT_BUCKET_SORT_ORDER); final AlbumEntry album = new AlbumEntry(); MediaSet mediaSet; if (cursor.moveToFirst()) { final int numAlbums = cursor.getCount(); final ArrayList<MediaSet> picasaSets = new ArrayList<MediaSet>(numAlbums); do { albumSchema.cursorToObject(cursor, album); String userLowerCase = album.syncAccount.toLowerCase(); final Boolean accountEnabledObj = accountsEnabled.get(userLowerCase); final boolean accountEnabled = (accountEnabledObj == null) ? false : accountEnabledObj.booleanValue(); if (accountEnabled) { mediaSet = feed.getMediaSet(album.id); if (mediaSet == null) { mediaSet = feed.addMediaSet(album.id, this); mediaSet.mName = album.title; mediaSet.mEditUri = album.editUri; mediaSet.generateTitle(true); } else { mediaSet.setNumExpectedItems(album.numPhotos); } mediaSet.mPicasaAlbumId = album.id; mediaSet.mIsLocal = false; mediaSet.mSyncPending = album.photosDirty; picasaSets.add(mediaSet); } } while (cursor.moveToNext()); } cursor.close(); } catch (RemoteException e) { Log.e(TAG, "Error occurred loading albums"); } } private void addItemsToFeed(MediaFeed feed, MediaSet set, int start, int end) { final ContentProviderClient client = mProviderClient; Cursor cursor = null; try { // Query photos in the album. final EntrySchema photosSchema = PhotoProjection.SCHEMA; final String whereInAlbum = "album_id = " + Long.toString(set.mId); cursor = client.query(PicasaContentProvider.PHOTOS_URI, photosSchema.getProjection(), whereInAlbum, null, null); final PhotoProjection photo = new PhotoProjection(); int count = cursor.getCount(); if (count < end) { end = count; } set.setNumExpectedItems(count); set.generateTitle(true); // Move to the next unread item. final int newIndex = start + 1; if (newIndex > count || !cursor.move(newIndex)) { end = 0; cursor.close(); set.updateNumExpectedItems(); set.generateTitle(true); return; } if (set.mNumItemsLoaded == 0) { photosSchema.cursorToObject(cursor, photo); set.mMinTimestamp = photo.dateTaken; cursor.moveToLast(); photosSchema.cursorToObject(cursor, photo); set.mMinTimestamp = photo.dateTaken; cursor.moveToFirst(); } for (int i = 0; i < end; ++i) { photosSchema.cursorToObject(cursor, photo); final MediaItem item = new MediaItem(); item.mId = photo.id; item.mEditUri = photo.editUri; item.mMimeType = photo.contentType; item.mDateTakenInMs = photo.dateTaken; item.mLatitude = photo.latitude; item.mLongitude = photo.longitude; item.mThumbnailUri = photo.thumbnailUrl; item.mScreennailUri = photo.screennailUrl; item.mContentUri = photo.contentUrl; item.mCaption = photo.title; item.mWeblink = photo.htmlPageUrl; item.mDescription = photo.summary; item.mFilePath = item.mContentUri; feed.addItemToMediaSet(item, set); if (!cursor.moveToNext()) { break; } } } catch (Exception e) { Log.e(TAG, "Error occurred loading photos for album " + set.mId); } finally { if (cursor != null) { cursor.close(); } } } public boolean performOperation(final int operation, final ArrayList<MediaBucket> mediaBuckets, final Object data) { try { if (operation == MediaFeed.OPERATION_DELETE) { ContentProviderClient client = mProviderClient; for (int i = 0, numBuckets = mediaBuckets.size(); i != numBuckets; ++i) { MediaBucket bucket = mediaBuckets.get(i); ArrayList<MediaItem> items = bucket.mediaItems; if (items == null) { // Delete an album. String albumUri = PicasaContentProvider.ALBUMS_URI + "/" + bucket.mediaSet.mId; client.delete(Uri.parse(albumUri), null, null); } else { // Delete a set of photos. for (int j = 0, numItems = items.size(); j != numItems; ++j) { MediaItem item = items.get(j); if (item != null) { String itemUri = PicasaContentProvider.PHOTOS_URI + "/" + item.mId; client.delete(Uri.parse(itemUri), null, null); } } } } } return true; } catch (RemoteException e) { return false; } } public DiskCache getThumbnailCache() { return sThumbnailCache; } /** * The projection of PhotoEntry needed by the data source. */ private static final class PhotoProjection extends Entry { public static final EntrySchema SCHEMA = new EntrySchema(PhotoProjection.class); @Column("edit_uri") public String editUri; @Column("title") public String title; @Column("summary") public String summary; @Column("date_taken") public long dateTaken; @Column("latitude") public double latitude; @Column("longitude") public double longitude; @Column("thumbnail_url") public String thumbnailUrl; @Column("screennail_url") public String screennailUrl; @Column("content_url") public String contentUrl; @Column("content_type") public String contentType; @Column("html_page_url") public String htmlPageUrl; } public String[] getDatabaseUris() { return new String[] { PicasaContentProvider.ALBUMS_URI.toString(), PicasaContentProvider.PHOTOS_URI.toString()}; } public void refresh(final MediaFeed feed, final String[] databaseUris) { // Depending on what URI changed, we either need to update the mediasets or the mediaitems of a set. if (databaseUris != null && databaseUris.length > 0) { if (ArrayUtils.contains(databaseUris, PicasaContentProvider.ALBUMS_URI.toString())) { // We need to refresh all mediasets of this datasource type. final ArrayList<MediaSet> mediaSets = feed.getMediaSets(); final int numMediaSets = mediaSets.size(); for (int i = 0; i < numMediaSets; ++i) { final MediaSet set = mediaSets.get(i); if (set.mDataSource instanceof PicasaDataSource) { set.mDataSource = this; set.refresh(); } } } else if (ArrayUtils.contains(databaseUris, PicasaContentProvider.PHOTOS_URI.toString())) { // We need to update just the one set that has these photos. // This operation is not yet supported, so we might as well refresh everything. final ArrayList<MediaSet> mediaSets = feed.getMediaSets(); final int numMediaSets = mediaSets.size(); for (int i = 0; i < numMediaSets; ++i) { final MediaSet set = mediaSets.get(i); if (set.mDataSource instanceof PicasaDataSource) { set.mDataSource = this; set.refresh(); } } } } } }