PicasaSource.java revision 146ce64b748e2393e80174240624c2e9f2b61c41
1/*
2 * Copyright (C) 2012 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 */
16package com.android.dreams.phototable;
17
18import android.content.Context;
19import android.content.SharedPreferences;
20import android.database.Cursor;
21import android.net.Uri;
22import android.util.Log;
23
24import java.io.FileNotFoundException;
25import java.io.InputStream;
26import java.io.IOException;
27import java.util.Collection;
28import java.util.Collections;
29import java.util.HashMap;
30import java.util.LinkedList;
31import java.util.Set;
32
33/**
34 * Loads images from Picasa.
35 */
36public class PicasaSource extends PhotoSource {
37    private static final String TAG = "PhotoTable.PicasaSource";
38
39    private static final String PICASA_AUTHORITY =
40            "com.google.android.gallery3d.GooglePhotoProvider";
41
42    private static final String PICASA_PHOTO_PATH = "photos";
43    private static final String PICASA_ALBUM_PATH = "albums";
44    private static final String PICASA_USER_PATH = "users";
45
46    private static final String PICASA_ID = "_id";
47    private static final String PICASA_URL = "content_url";
48    private static final String PICASA_ROTATION = "rotation";
49    private static final String PICASA_ALBUM_ID = "album_id";
50    private static final String PICASA_TITLE = "title";
51    private static final String PICASA_THUMB = "thumbnail_url";
52    private static final String PICASA_ALBUM_TYPE = "album_type";
53    private static final String PICASA_ALBUM_USER = "user_id";
54    private static final String PICASA_ALBUM_UPDATED = "date_updated";
55    private static final String PICASA_ACCOUNT = "account";
56
57    private static final String PICASA_URL_KEY = "content_url";
58    private static final String PICASA_TYPE_KEY = "type";
59    private static final String PICASA_TYPE_FULL_VALUE = "full";
60    private static final String PICASA_TYPE_SCREEN_VALUE = "screennail";
61    private static final String PICASA_TYPE_THUMB_VALUE = "thumbnail";
62    private static final String PICASA_TYPE_IMAGE_VALUE = "image";
63    private static final String PICASA_POSTS_TYPE = "Buzz";
64    private static final String PICASA_UPLOAD_TYPE = "InstantUpload";
65
66    private final int mMaxPostAblums;
67    private final String mPostsAlbumName;
68    private final String mUploadsAlbumName;
69    private final String mUnknownAlbumName;
70
71    private Set<String> mFoundAlbumIds;
72    private int mNextPosition;
73
74    public PicasaSource(Context context, SharedPreferences settings) {
75        super(context, settings);
76        mSourceName = TAG;
77        mNextPosition = -1;
78        mMaxPostAblums = mResources.getInteger(R.integer.max_post_albums);
79        mPostsAlbumName = mResources.getString(R.string.posts_album_name, "Posts");
80        mUploadsAlbumName = mResources.getString(R.string.uploads_album_name, "Instant Uploads");
81        mUnknownAlbumName = mResources.getString(R.string.unknown_album_name, "Unknown");
82        fillQueue();
83    }
84
85    @Override
86    protected Collection<ImageData> findImages(int howMany) {
87        log(TAG, "finding images");
88        LinkedList<ImageData> foundImages = new LinkedList<ImageData>();
89        String[] projection = {PICASA_ID, PICASA_URL, PICASA_ROTATION, PICASA_ALBUM_ID};
90        boolean usePosts = false;
91        LinkedList<String> albumIds = new LinkedList<String>();
92        for (String id : getFoundAlbums()) {
93            if (mSettings.isAlbumEnabled(id)) {
94                String[] parts = id.split(":");
95                if (parts.length > 2) {
96                    albumIds.addAll(resolveAlbumIds(id));
97                } else {
98                    albumIds.add(parts[1]);
99                }
100            }
101        }
102
103        if (albumIds.size() > mMaxPostAblums) {
104            Collections.shuffle(albumIds);
105        }
106
107        StringBuilder selection = new StringBuilder();
108        int albumIdx = 0;
109        for (String albumId : albumIds) {
110            if (albumIdx < mMaxPostAblums) {
111                if (selection.length() > 0) {
112                    selection.append(" OR ");
113                }
114                log(TAG, "adding: " + albumId);
115                selection.append(PICASA_ALBUM_ID + " = '" + albumId + "'");
116            } else {
117                log(TAG, "too many albums, dropping: " + albumId);
118            }
119            albumIdx++;
120        }
121
122        if (selection.length() == 0) {
123            return foundImages;
124        }
125
126        log(TAG, "selection is (" + selection.length() + "): " + selection.toString());
127
128        Uri.Builder picasaUriBuilder = new Uri.Builder()
129                .scheme("content")
130                .authority(PICASA_AUTHORITY)
131                .appendPath(PICASA_PHOTO_PATH);
132        Cursor cursor = mResolver.query(picasaUriBuilder.build(),
133                projection, selection.toString(), null, null);
134        if (cursor != null) {
135            if (cursor.getCount() > howMany && mNextPosition == -1) {
136                mNextPosition =
137                        (int) Math.abs(mRNG.nextInt() % (cursor.getCount() - howMany));
138            }
139            if (mNextPosition == -1) {
140                mNextPosition = 0;
141            }
142            log(TAG, "moving to position: " + mNextPosition);
143            cursor.moveToPosition(mNextPosition);
144
145            int idIndex = cursor.getColumnIndex(PICASA_ID);
146            int urlIndex = cursor.getColumnIndex(PICASA_URL);
147            int orientationIndex = cursor.getColumnIndex(PICASA_ROTATION);
148            int bucketIndex = cursor.getColumnIndex(PICASA_ALBUM_ID);
149
150            if (idIndex < 0) {
151                log(TAG, "can't find the ID column!");
152            } else {
153                while (foundImages.size() < howMany && !cursor.isAfterLast()) {
154                    if (idIndex >= 0) {
155                        ImageData data = new ImageData();
156                        data.id = cursor.getString(idIndex);
157
158                        if (urlIndex >= 0) {
159                            data.url = cursor.getString(urlIndex);
160                        }
161
162                        foundImages.offer(data);
163                    }
164                    if (cursor.moveToNext()) {
165                        mNextPosition++;
166                    }
167                }
168                if (cursor.isAfterLast()) {
169                    mNextPosition = 0;
170                }
171            }
172
173            cursor.close();
174        } else {
175            Log.w(TAG, "received a null cursor in findImages()");
176        }
177        log(TAG, "found " + foundImages.size() + " items.");
178        return foundImages;
179    }
180
181    private String resolveAccount(String id) {
182        String displayName = "unknown";
183        String[] projection = {PICASA_ACCOUNT};
184        Uri.Builder picasaUriBuilder = new Uri.Builder()
185                .scheme("content")
186                .authority(PICASA_AUTHORITY)
187                .appendPath(PICASA_USER_PATH)
188                .appendPath(id);
189        Cursor cursor = mResolver.query(picasaUriBuilder.build(),
190                projection, null, null, null);
191        if (cursor != null) {
192            cursor.moveToFirst();
193            int accountIndex = cursor.getColumnIndex(PICASA_ACCOUNT);
194            if (accountIndex >= 0) {
195                displayName = cursor.getString(accountIndex);
196            }
197            cursor.close();
198        } else {
199            Log.w(TAG, "received a null cursor in resolveAccount()");
200        }
201        return displayName;
202    }
203
204    private Collection<String> resolveAlbumIds(String id) {
205        LinkedList<String> albumIds = new LinkedList<String>();
206        log(TAG, "resolving " + id);
207
208        String[] parts = id.split(":");
209        if (parts.length < 3) {
210            return albumIds;
211        }
212
213        String[] projection = {PICASA_ID, PICASA_ALBUM_TYPE, PICASA_ALBUM_UPDATED,
214                               PICASA_ALBUM_USER};
215        String order = PICASA_ALBUM_UPDATED + " DESC";
216        String selection = (PICASA_ALBUM_USER + " = '" + parts[2] + "' AND " +
217                            PICASA_ALBUM_TYPE + " = '" + parts[1] + "'");
218        Uri.Builder picasaUriBuilder = new Uri.Builder()
219                .scheme("content")
220                .authority(PICASA_AUTHORITY)
221                .appendPath(PICASA_ALBUM_PATH)
222                .appendQueryParameter(PICASA_TYPE_KEY, PICASA_TYPE_IMAGE_VALUE);
223        Cursor cursor = mResolver.query(picasaUriBuilder.build(),
224                projection, selection, null, order);
225        if (cursor != null) {
226            log(TAG, " " + id + " resolved to " + cursor.getCount() + " albums");
227            cursor.moveToFirst();
228
229            int idIndex = cursor.getColumnIndex(PICASA_ID);
230            int typeIndex = cursor.getColumnIndex(PICASA_ALBUM_TYPE);
231
232            if (idIndex < 0) {
233                log(TAG, "can't find the ID column!");
234            } else {
235                while (!cursor.isAfterLast()) {
236                    albumIds.add(cursor.getString(idIndex));
237                    cursor.moveToNext();
238                }
239            }
240            cursor.close();
241        } else {
242            Log.w(TAG, "received a null cursor in resolveAlbumIds()");
243        }
244        return albumIds;
245    }
246
247    private Set<String> getFoundAlbums() {
248        if (mFoundAlbumIds == null) {
249            findAlbums();
250        }
251        return mFoundAlbumIds;
252    }
253
254    @Override
255    public Collection<AlbumData> findAlbums() {
256        log(TAG, "finding albums");
257        HashMap<String, AlbumData> foundAlbums = new HashMap<String, AlbumData>();
258        HashMap<String, String> accounts = new HashMap<String, String>();
259        String[] projection = {PICASA_ID, PICASA_TITLE, PICASA_THUMB, PICASA_ALBUM_TYPE,
260                               PICASA_ALBUM_USER, PICASA_ALBUM_UPDATED};
261        Uri.Builder picasaUriBuilder = new Uri.Builder()
262                .scheme("content")
263                .authority(PICASA_AUTHORITY)
264                .appendPath(PICASA_ALBUM_PATH)
265                .appendQueryParameter(PICASA_TYPE_KEY, PICASA_TYPE_IMAGE_VALUE);
266        Cursor cursor = mResolver.query(picasaUriBuilder.build(),
267                projection, null, null, null);
268        if (cursor != null) {
269            cursor.moveToFirst();
270
271            int idIndex = cursor.getColumnIndex(PICASA_ID);
272            int thumbIndex = cursor.getColumnIndex(PICASA_THUMB);
273            int titleIndex = cursor.getColumnIndex(PICASA_TITLE);
274            int typeIndex = cursor.getColumnIndex(PICASA_ALBUM_TYPE);
275            int updatedIndex = cursor.getColumnIndex(PICASA_ALBUM_UPDATED);
276            int userIndex = cursor.getColumnIndex(PICASA_ALBUM_USER);
277
278            if (idIndex < 0) {
279                log(TAG, "can't find the ID column!");
280            } else {
281                while (!cursor.isAfterLast()) {
282                    String id = TAG + ":" + cursor.getString(idIndex);
283                    String user = (userIndex >= 0 ? cursor.getString(userIndex) : "-1");
284                    String type = (typeIndex >= 0 ? cursor.getString(typeIndex) : "none");
285                    boolean isPosts = (typeIndex >= 0 && PICASA_POSTS_TYPE.equals(type));
286                    boolean isUpload = (typeIndex >= 0 && PICASA_UPLOAD_TYPE.equals(type));
287
288                    String account = accounts.get(user);
289                    if (account == null) {
290                        account = resolveAccount(user);
291                        accounts.put(user, account);
292                    }
293
294                    if (isPosts) {
295                        log(TAG, "replacing " + id + " with " + PICASA_POSTS_TYPE);
296                        id = TAG + ":" + PICASA_POSTS_TYPE + ":" + user;
297                    }
298
299                    if (isUpload) {
300                        log(TAG, "replacing " + id + " with " + PICASA_UPLOAD_TYPE);
301                        id = TAG + ":" + PICASA_UPLOAD_TYPE + ":" + user;
302                    }
303
304                    String thumbnailUrl = null;
305                    long updated = 0;
306                    AlbumData data = foundAlbums.get(id);
307                    if (data == null) {
308                        data = new AlbumData();
309                        data.id = id;
310                        data.account = account;
311
312                        if (isPosts) {
313                            data.title = mPostsAlbumName;
314                        } else if (isUpload) {
315                            data.title = mUploadsAlbumName;
316                        } else if (titleIndex >= 0) {
317                            data.title = cursor.getString(titleIndex);
318                        } else {
319                            data.title = mUnknownAlbumName;
320                        }
321
322                        log(TAG, "found " + data.title + "(" + data.id + ")" +
323                                " of type " + type + " owned by " + user);
324                        foundAlbums.put(id, data);
325                    }
326
327                    if (updatedIndex >= 0) {
328                        updated = cursor.getLong(updatedIndex);
329                    }
330
331                    if (thumbIndex >= 0) {
332                        thumbnailUrl = cursor.getString(thumbIndex);
333                    }
334
335                    data.updated = (long) Math.max(data.updated, updated);
336
337                    if (data.thumbnailUrl == null || data.updated == updated) {
338                        data.thumbnailUrl = thumbnailUrl;
339                    }
340
341                    cursor.moveToNext();
342                }
343            }
344            cursor.close();
345
346        } else {
347            Log.w(TAG, "received a null cursor in findAlbums()");
348        }
349        log(TAG, "found " + foundAlbums.size() + " items.");
350        mFoundAlbumIds = foundAlbums.keySet();
351        return foundAlbums.values();
352    }
353
354    @Override
355    protected InputStream getStream(ImageData data) {
356        InputStream is = null;
357        try {
358            Uri.Builder photoUriBuilder = new Uri.Builder()
359                    .scheme("content")
360                    .authority(PICASA_AUTHORITY)
361                    .appendPath(PICASA_PHOTO_PATH)
362                    .appendPath(data.id)
363                    .appendQueryParameter(PICASA_TYPE_KEY, PICASA_TYPE_FULL_VALUE);
364            if (data.url != null) {
365                photoUriBuilder.appendQueryParameter(PICASA_URL_KEY, data.url);
366            }
367            is = mResolver.openInputStream(photoUriBuilder.build());
368        } catch (FileNotFoundException fnf) {
369            log(TAG, "file not found: " + fnf);
370            is = null;
371        } catch (IOException ioe) {
372            log(TAG, "i/o exception: " + ioe);
373            is = null;
374        }
375
376        return is;
377    }
378}
379