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