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