• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
16 package com.android.dreams.phototable;
17 
18 import android.content.Context;
19 import android.content.SharedPreferences;
20 import android.database.Cursor;
21 import android.net.ConnectivityManager;
22 import android.net.Uri;
23 import android.util.DisplayMetrics;
24 import android.util.Log;
25 import android.view.WindowManager;
26 
27 import java.io.FileNotFoundException;
28 import java.io.InputStream;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.LinkedList;
33 import java.util.Set;
34 
35 /**
36  * Loads images from Picasa.
37  */
38 public class PicasaSource extends CursorPhotoSource {
39     private static final String TAG = "PhotoTable.PicasaSource";
40 
41     private static final String PICASA_AUTHORITY =
42             "com.google.android.gallery3d.GooglePhotoProvider";
43 
44     private static final String PICASA_PHOTO_PATH = "photos";
45     private static final String PICASA_ALBUM_PATH = "albums";
46     private static final String PICASA_USER_PATH = "users";
47 
48     private static final String PICASA_ID = "_id";
49     private static final String PICASA_URL = "content_url";
50     private static final String PICASA_ROTATION = "rotation";
51     private static final String PICASA_ALBUM_ID = "album_id";
52     private static final String PICASA_TITLE = "title";
53     private static final String PICASA_THUMB = "thumbnail_url";
54     private static final String PICASA_ALBUM_TYPE = "album_type";
55     private static final String PICASA_ALBUM_USER = "user_id";
56     private static final String PICASA_ALBUM_UPDATED = "date_updated";
57     private static final String PICASA_ACCOUNT = "account";
58 
59     private static final String PICASA_URL_KEY = "content_url";
60     private static final String PICASA_TYPE_KEY = "type";
61     private static final String PICASA_TYPE_FULL_VALUE = "full";
62     private static final String PICASA_TYPE_SCREEN_VALUE = "screennail";
63     private static final String PICASA_TYPE_IMAGE_VALUE = "image";
64     private static final String PICASA_POSTS_TYPE = "Buzz";
65     private static final String PICASA_UPLOAD_TYPE = "InstantUpload";
66     private static final String PICASA_UPLOADAUTO_TYPE = "InstantUploadAuto";
67 
68     private final int mMaxPostAblums;
69     private final String mPostsAlbumName;
70     private final String mUploadsAlbumName;
71     private final String mUnknownAlbumName;
72     private final LinkedList<ImageData> mRecycleBin;
73     private final ConnectivityManager mConnectivityManager;
74     private final int mMaxRecycleSize;
75 
76     private Set<String> mFoundAlbumIds;
77     private int mLastPosition;
78     private int mDisplayLongSide;
79 
PicasaSource(Context context, SharedPreferences settings)80     public PicasaSource(Context context, SharedPreferences settings) {
81         super(context, settings);
82         mSourceName = TAG;
83         mLastPosition = INVALID;
84         mMaxPostAblums = mResources.getInteger(R.integer.max_post_albums);
85         mPostsAlbumName = mResources.getString(R.string.posts_album_name, "Posts");
86         mUploadsAlbumName = mResources.getString(R.string.uploads_album_name, "Instant Uploads");
87         mUnknownAlbumName = mResources.getString(R.string.unknown_album_name, "Unknown");
88         mMaxRecycleSize = mResources.getInteger(R.integer.recycle_image_pool_size);
89         mConnectivityManager =
90                 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
91         mRecycleBin = new LinkedList<ImageData>();
92 
93         fillQueue();
94         mDisplayLongSide = getDisplayLongSide();
95     }
96 
getDisplayLongSide()97     private int getDisplayLongSide() {
98         DisplayMetrics metrics = new DisplayMetrics();
99         WindowManager wm = (WindowManager)
100                 mContext.getSystemService(Context.WINDOW_SERVICE);
101         wm.getDefaultDisplay().getMetrics(metrics);
102         return Math.max(metrics.heightPixels, metrics.widthPixels);
103     }
104 
105     @Override
openCursor(ImageData data)106     protected void openCursor(ImageData data) {
107         log(TAG, "opening single album");
108 
109         String[] projection = {PICASA_ID, PICASA_URL, PICASA_ROTATION, PICASA_ALBUM_ID};
110         String selection = PICASA_ALBUM_ID + " = '" + data.albumId + "'";
111 
112         Uri.Builder picasaUriBuilder = new Uri.Builder()
113                 .scheme("content")
114                 .authority(PICASA_AUTHORITY)
115                 .appendPath(PICASA_PHOTO_PATH);
116         data.cursor = mResolver.query(picasaUriBuilder.build(),
117                 projection, selection, null, null);
118     }
119 
120     @Override
findPosition(ImageData data)121     protected void findPosition(ImageData data) {
122         if (data.position == UNINITIALIZED) {
123             if (data.cursor == null) {
124                 openCursor(data);
125             }
126             if (data.cursor != null) {
127                 int idIndex = data.cursor.getColumnIndex(PICASA_ID);
128                 data.cursor.moveToPosition(-1);
129                 while (data.position == -1 && data.cursor.moveToNext()) {
130                     String id = data.cursor.getString(idIndex);
131                     if (id != null && id.equals(data.id)) {
132                         data.position = data.cursor.getPosition();
133                     }
134                 }
135                 if (data.position == -1) {
136                     // oops!  The image isn't in this album. How did we get here?
137                     data.position = INVALID;
138                 }
139             }
140         }
141     }
142 
143     @Override
unpackImageData(Cursor cursor, ImageData data)144     protected ImageData unpackImageData(Cursor cursor, ImageData data) {
145         if (data == null) {
146             data = new ImageData();
147         }
148         int idIndex = cursor.getColumnIndex(PICASA_ID);
149         int urlIndex = cursor.getColumnIndex(PICASA_URL);
150         int bucketIndex = cursor.getColumnIndex(PICASA_ALBUM_ID);
151 
152         data.id = cursor.getString(idIndex);
153         if (bucketIndex >= 0) {
154             data.albumId = cursor.getString(bucketIndex);
155         }
156         if (urlIndex >= 0) {
157             data.url = cursor.getString(urlIndex);
158         }
159         data.position = UNINITIALIZED;
160         data.cursor = null;
161         return data;
162     }
163 
164     @Override
findImages(int howMany)165     protected Collection<ImageData> findImages(int howMany) {
166         log(TAG, "finding images");
167         LinkedList<ImageData> foundImages = new LinkedList<ImageData>();
168         if (mConnectivityManager.isActiveNetworkMetered()) {
169             howMany = Math.min(howMany, mMaxRecycleSize);
170             log(TAG, "METERED: " + howMany);
171             if (!mRecycleBin.isEmpty()) {
172                 foundImages.addAll(mRecycleBin);
173                 log(TAG, "recycled " + foundImages.size() + " items.");
174                 return foundImages;
175             }
176         }
177 
178         String[] projection = {PICASA_ID, PICASA_URL, PICASA_ROTATION, PICASA_ALBUM_ID};
179         LinkedList<String> albumIds = new LinkedList<String>();
180         for (String id : getFoundAlbums()) {
181             if (mSettings.isAlbumEnabled(id)) {
182                 String[] parts = id.split(":");
183                 if (parts.length > 2) {
184                     albumIds.addAll(resolveAlbumIds(id));
185                 } else {
186                     albumIds.add(parts[1]);
187                 }
188             }
189         }
190 
191         if (albumIds.size() > mMaxPostAblums) {
192             Collections.shuffle(albumIds);
193         }
194 
195         StringBuilder selection = new StringBuilder();
196         int albumIdx = 0;
197         for (String albumId : albumIds) {
198             if (albumIdx < mMaxPostAblums) {
199                 if (selection.length() > 0) {
200                     selection.append(" OR ");
201                 }
202                 log(TAG, "adding: " + albumId);
203                 selection.append(PICASA_ALBUM_ID + " = '" + albumId + "'");
204             } else {
205                 log(TAG, "too many albums, dropping: " + albumId);
206             }
207             albumIdx++;
208         }
209 
210         if (selection.length() == 0) {
211             return foundImages;
212         }
213 
214         log(TAG, "selection is (" + selection.length() + "): " + selection.toString());
215 
216         Uri.Builder picasaUriBuilder = new Uri.Builder()
217                 .scheme("content")
218                 .authority(PICASA_AUTHORITY)
219                 .appendPath(PICASA_PHOTO_PATH);
220         Cursor cursor = mResolver.query(picasaUriBuilder.build(),
221                 projection, selection.toString(), null, null);
222         if (cursor != null) {
223             if (cursor.getCount() > howMany && mLastPosition == INVALID) {
224                 mLastPosition = pickRandomStart(cursor.getCount(), howMany);
225             }
226 
227             log(TAG, "moving to position: " + mLastPosition);
228             cursor.moveToPosition(mLastPosition);
229 
230             int idIndex = cursor.getColumnIndex(PICASA_ID);
231 
232             if (idIndex < 0) {
233                 log(TAG, "can't find the ID column!");
234             } else {
235                 while (cursor.moveToNext()) {
236                     if (idIndex >= 0) {
237                         ImageData data = unpackImageData(cursor, null);
238                         foundImages.offer(data);
239                     }
240                     mLastPosition = cursor.getPosition();
241                 }
242                 if (cursor.isAfterLast()) {
243                     mLastPosition = -1;
244                 }
245                 if (cursor.isBeforeFirst()) {
246                     mLastPosition = INVALID;
247                 }
248             }
249 
250             cursor.close();
251         } else {
252             Log.w(TAG, "received a null cursor in findImages()");
253         }
254         log(TAG, "found " + foundImages.size() + " items.");
255         return foundImages;
256     }
257 
resolveAccount(String id)258     private String resolveAccount(String id) {
259         String displayName = "unknown";
260         String[] projection = {PICASA_ACCOUNT};
261         Uri.Builder picasaUriBuilder = new Uri.Builder()
262                 .scheme("content")
263                 .authority(PICASA_AUTHORITY)
264                 .appendPath(PICASA_USER_PATH)
265                 .appendPath(id);
266         Cursor cursor = mResolver.query(picasaUriBuilder.build(),
267                 projection, null, null, null);
268         if (cursor != null) {
269             cursor.moveToFirst();
270             int accountIndex = cursor.getColumnIndex(PICASA_ACCOUNT);
271             if (accountIndex >= 0) {
272                 displayName = cursor.getString(accountIndex);
273             }
274             cursor.close();
275         } else {
276             Log.w(TAG, "received a null cursor in resolveAccount()");
277         }
278         return displayName;
279     }
280 
resolveAlbumIds(String id)281     private Collection<String> resolveAlbumIds(String id) {
282         LinkedList<String> albumIds = new LinkedList<String>();
283         log(TAG, "resolving " + id);
284 
285         String[] parts = id.split(":");
286         if (parts.length < 3) {
287             return albumIds;
288         }
289 
290         String[] projection = {PICASA_ID, PICASA_ALBUM_TYPE, PICASA_ALBUM_UPDATED,
291                                PICASA_ALBUM_USER};
292         String order = PICASA_ALBUM_UPDATED + " DESC";
293         String selection = (PICASA_ALBUM_USER + " = '" + parts[2] + "' AND " +
294                             PICASA_ALBUM_TYPE + " = '" + parts[1] + "'");
295         Uri.Builder picasaUriBuilder = new Uri.Builder()
296                 .scheme("content")
297                 .authority(PICASA_AUTHORITY)
298                 .appendPath(PICASA_ALBUM_PATH)
299                 .appendQueryParameter(PICASA_TYPE_KEY, PICASA_TYPE_IMAGE_VALUE);
300         Cursor cursor = mResolver.query(picasaUriBuilder.build(),
301                 projection, selection, null, order);
302         if (cursor != null) {
303             log(TAG, " " + id + " resolved to " + cursor.getCount() + " albums");
304             cursor.moveToPosition(-1);
305 
306             int idIndex = cursor.getColumnIndex(PICASA_ID);
307 
308             if (idIndex < 0) {
309                 log(TAG, "can't find the ID column!");
310             } else {
311                 while (cursor.moveToNext()) {
312                     albumIds.add(cursor.getString(idIndex));
313                 }
314             }
315             cursor.close();
316         } else {
317             Log.w(TAG, "received a null cursor in resolveAlbumIds()");
318         }
319         return albumIds;
320     }
321 
getFoundAlbums()322     private Set<String> getFoundAlbums() {
323         if (mFoundAlbumIds == null) {
324             findAlbums();
325         }
326         return mFoundAlbumIds;
327     }
328 
329     @Override
findAlbums()330     public Collection<AlbumData> findAlbums() {
331         log(TAG, "finding albums");
332         HashMap<String, AlbumData> foundAlbums = new HashMap<String, AlbumData>();
333         HashMap<String, String> accounts = new HashMap<String, String>();
334         String[] projection = {PICASA_ID, PICASA_TITLE, PICASA_THUMB, PICASA_ALBUM_TYPE,
335                                PICASA_ALBUM_USER, PICASA_ALBUM_UPDATED};
336         Uri.Builder picasaUriBuilder = new Uri.Builder()
337                 .scheme("content")
338                 .authority(PICASA_AUTHORITY)
339                 .appendPath(PICASA_ALBUM_PATH)
340                 .appendQueryParameter(PICASA_TYPE_KEY, PICASA_TYPE_IMAGE_VALUE);
341         Cursor cursor = mResolver.query(picasaUriBuilder.build(),
342                 projection, null, null, null);
343         if (cursor != null) {
344             cursor.moveToPosition(-1);
345 
346             int idIndex = cursor.getColumnIndex(PICASA_ID);
347             int thumbIndex = cursor.getColumnIndex(PICASA_THUMB);
348             int titleIndex = cursor.getColumnIndex(PICASA_TITLE);
349             int typeIndex = cursor.getColumnIndex(PICASA_ALBUM_TYPE);
350             int updatedIndex = cursor.getColumnIndex(PICASA_ALBUM_UPDATED);
351             int userIndex = cursor.getColumnIndex(PICASA_ALBUM_USER);
352 
353             if (idIndex < 0) {
354                 log(TAG, "can't find the ID column!");
355             } else {
356                 while (cursor.moveToNext()) {
357                     String id = TAG + ":" + cursor.getString(idIndex);
358                     String user = (userIndex >= 0 ? cursor.getString(userIndex) : "-1");
359                     String type = (typeIndex >= 0 ? cursor.getString(typeIndex) : "none");
360                     boolean isPosts = (typeIndex >= 0 && PICASA_POSTS_TYPE.equals(type));
361                     boolean isUpload = (typeIndex >= 0 &&
362                             (PICASA_UPLOAD_TYPE.equals(type) || PICASA_UPLOADAUTO_TYPE.equals(type)));
363 
364                     String account = accounts.get(user);
365                     if (account == null) {
366                         account = resolveAccount(user);
367                         accounts.put(user, account);
368                     }
369 
370                     if (isPosts) {
371                         log(TAG, "replacing " + id + " with " + PICASA_POSTS_TYPE);
372                         id = TAG + ":" + PICASA_POSTS_TYPE + ":" + user;
373                     }
374 
375                     if (isUpload) {
376                         log(TAG, "replacing " + id + " with " + PICASA_UPLOAD_TYPE);
377                         id = TAG + ":" + PICASA_UPLOAD_TYPE + ":" + user;
378                     }
379 
380                     String thumbnailUrl = null;
381                     long updated = 0;
382                     AlbumData data = foundAlbums.get(id);
383                     if (data == null) {
384                         data = new AlbumData();
385                         data.id = id;
386                         data.account = account;
387 
388                         if (isPosts) {
389                             data.title = mPostsAlbumName;
390                         } else if (isUpload) {
391                             data.title = mUploadsAlbumName;
392                         } else if (titleIndex >= 0) {
393                             data.title = cursor.getString(titleIndex);
394                         } else {
395                             data.title = mUnknownAlbumName;
396                         }
397 
398                         log(TAG, "found " + data.title + "(" + data.id + ")" +
399                                 " of type " + type + " owned by " + user);
400                         foundAlbums.put(id, data);
401                     }
402 
403                     if (updatedIndex >= 0) {
404                         updated = cursor.getLong(updatedIndex);
405                     }
406 
407                     if (thumbIndex >= 0) {
408                         thumbnailUrl = cursor.getString(thumbIndex);
409                     }
410 
411                     data.updated = (long) Math.max(data.updated, updated);
412 
413                     if (data.thumbnailUrl == null || data.updated == updated) {
414                         data.thumbnailUrl = thumbnailUrl;
415                     }
416                 }
417             }
418             cursor.close();
419 
420         } else {
421             Log.w(TAG, "received a null cursor in findAlbums()");
422         }
423         log(TAG, "found " + foundAlbums.size() + " items.");
424         mFoundAlbumIds = foundAlbums.keySet();
425         return foundAlbums.values();
426     }
427 
428     @Override
getStream(ImageData data, int longSide)429     protected InputStream getStream(ImageData data, int longSide) {
430         InputStream is = null;
431         try {
432             Uri.Builder photoUriBuilder = new Uri.Builder()
433                     .scheme("content")
434                     .authority(PICASA_AUTHORITY)
435                     .appendPath(PICASA_PHOTO_PATH)
436                     .appendPath(data.id);
437             if (mConnectivityManager.isActiveNetworkMetered() ||
438                     ((2 * longSide) <= mDisplayLongSide)) {
439                 photoUriBuilder.appendQueryParameter(PICASA_TYPE_KEY, PICASA_TYPE_SCREEN_VALUE);
440             } else {
441                 photoUriBuilder.appendQueryParameter(PICASA_TYPE_KEY, PICASA_TYPE_FULL_VALUE);
442             }
443             if (data.url != null) {
444                 photoUriBuilder.appendQueryParameter(PICASA_URL_KEY, data.url);
445             }
446             is = mResolver.openInputStream(photoUriBuilder.build());
447         } catch (FileNotFoundException fnf) {
448             log(TAG, "file not found: " + fnf);
449             is = null;
450         }
451 
452         if (is != null) {
453             mRecycleBin.offer(data);
454             log(TAG, "RECYCLED");
455             while (mRecycleBin.size() > mMaxRecycleSize) {
456                 mRecycleBin.poll();
457             }
458         }
459         return is;
460     }
461 }
462