1 package com.cooliris.picasa;
3 import java.io.IOException;
4 import java.util.ArrayList;
6 import org.apache.http.HttpStatus;
7 import org.xml.sax.SAXException;
9 import android.accounts.Account;
10 import android.accounts.AccountManager;
11 import android.accounts.AuthenticatorException;
12 import android.accounts.OperationCanceledException;
13 import android.app.Activity;
14 import android.content.Context;
15 import android.content.SyncResult;
16 import android.net.Uri;
17 import android.os.Bundle;
18 import android.util.Log;
19 import android.util.Xml;
21 public final class PicasaApi {
22 public static final int RESULT_OK = 0;
23 public static final int RESULT_NOT_MODIFIED = 1;
24 public static final int RESULT_ERROR = 2;
26 private static final String TAG = "PicasaAPI";
27 private static final String BASE_URL = "http://picasaweb.google.com/data/feed/api/";
28 private static final String BASE_QUERY_STRING;
31 // Build the base query string using screen dimensions.
32 final StringBuilder query = new StringBuilder("?imgmax=1024&max-results=1000&thumbsize=");
33 final String thumbnailSize = "144u,";
34 final String screennailSize = "1024u";
35 query.append(thumbnailSize);
36 query.append(screennailSize);
37 BASE_QUERY_STRING = query.toString() + "&visibility=visible";
40 private final GDataClient mClient;
41 private final GDataClient.Operation mOperation = new GDataClient.Operation();
42 private final GDataParser mParser = new GDataParser();
43 private final AlbumEntry mAlbumInstance = new AlbumEntry();
44 private final PhotoEntry mPhotoInstance = new PhotoEntry();
45 private AuthAccount mAuth;
47 public static final class AuthAccount {
48 public final String user;
49 public final String authToken;
50 public final Account account;
52 public AuthAccount(String user, String authToken, Account account) {
54 this.authToken = authToken;
55 this.account = account;
59 public static Account[] getAccounts(Context context) {
60 // Return the list of accounts supporting the Picasa GData service.
61 AccountManager accountManager = AccountManager.get(context);
62 Account[] accounts = {};
64 accounts = accountManager.getAccountsByTypeAndFeatures(PicasaService.ACCOUNT_TYPE,
65 new String[] { PicasaService.FEATURE_SERVICE_NAME }, null, null).getResult();
66 } catch (OperationCanceledException e) {
67 } catch (AuthenticatorException e) {
68 } catch (IOException e) {
73 public static AuthAccount[] getAuthenticatedAccounts(Context context) {
74 AccountManager accountManager = AccountManager.get(context);
75 Account[] accounts = getAccounts(context);
76 int numAccounts = accounts.length;
78 ArrayList<AuthAccount> authAccounts = new ArrayList<AuthAccount>(numAccounts);
79 for (int i = 0; i != numAccounts; ++i) {
80 Account account = accounts[i];
83 // Get the token without user interaction.
84 authToken = accountManager.blockingGetAuthToken(account, PicasaService.SERVICE_NAME, true);
86 // TODO: Remove this once the build is signed by Google, since
87 // we will always have permission.
88 // This code requests permission from the user explicitly.
89 if (context instanceof Activity) {
90 Bundle bundle = accountManager.getAuthToken(account, PicasaService.SERVICE_NAME, null, (Activity) context,
91 null, null).getResult();
92 authToken = bundle.getString("authtoken");
93 PicasaService.requestSync(context, PicasaService.TYPE_USERS_ALBUMS, -1);
96 // Add the account information to the list of accounts.
97 if (authToken != null) {
98 String username = account.name;
99 if (username.contains("@gmail.") || username.contains("@googlemail.")) {
100 // Strip the domain from GMail accounts for
101 // canonicalization. TODO: is there an official way?
102 username = username.substring(0, username.indexOf('@'));
104 authAccounts.add(new AuthAccount(username, authToken, account));
106 } catch (OperationCanceledException e) {
107 } catch (IOException e) {
108 } catch (AuthenticatorException e) {
111 AuthAccount[] authArray = new AuthAccount[authAccounts.size()];
112 authAccounts.toArray(authArray);
117 mClient = new GDataClient();
120 public void setAuth(AuthAccount auth) {
122 synchronized (mClient) {
123 mClient.setAuthToken(auth.authToken);
127 public int getAlbums(AccountManager accountManager, SyncResult syncResult, UserEntry user, GDataParser.EntryHandler handler) {
128 // Construct the query URL for user albums.
129 StringBuilder builder = new StringBuilder(BASE_URL);
130 builder.append("user/");
131 builder.append(Uri.encode(mAuth.user));
132 builder.append(BASE_QUERY_STRING);
133 builder.append("&kind=album");
136 synchronized (mOperation) {
137 GDataClient.Operation operation = mOperation;
138 operation.inOutEtag = user.albumsEtag;
139 boolean retry = false;
143 synchronized (mClient) {
144 mClient.get(builder.toString(), operation);
146 switch (operation.outStatus) {
147 case HttpStatus.SC_OK:
149 case HttpStatus.SC_NOT_MODIFIED:
150 return RESULT_NOT_MODIFIED;
151 case HttpStatus.SC_FORBIDDEN:
152 case HttpStatus.SC_UNAUTHORIZED:
154 accountManager.invalidateAuthToken(PicasaService.ACCOUNT_TYPE, mAuth.authToken);
157 if (numRetries == 0) {
158 ++syncResult.stats.numAuthExceptions;
161 Log.i(TAG, "getAlbums uri " + builder.toString());
162 Log.e(TAG, "getAlbums: unexpected status code " + operation.outStatus + " data: "
163 + operation.outBody.toString());
164 ++syncResult.stats.numIoExceptions;
168 } while (retry && numRetries >= 0);
170 // Store the new ETag for the user/albums feed.
171 user.albumsEtag = operation.inOutEtag;
173 // Parse the response.
174 synchronized (mParser) {
175 GDataParser parser = mParser;
176 parser.setEntry(mAlbumInstance);
177 parser.setHandler(handler);
178 Xml.parse(operation.outBody, Xml.Encoding.UTF_8, parser);
182 } catch (IOException e) {
183 Log.e(TAG, "getAlbums: " + e);
184 ++syncResult.stats.numIoExceptions;
185 } catch (SAXException e) {
186 Log.e(TAG, "getAlbums: " + e);
187 ++syncResult.stats.numParseExceptions;
192 public int getAlbumPhotos(AccountManager accountManager, SyncResult syncResult, AlbumEntry album,
193 GDataParser.EntryHandler handler) {
194 // Construct the query URL for user albums.
195 StringBuilder builder = new StringBuilder(BASE_URL);
196 builder.append("user/");
197 builder.append(Uri.encode(mAuth.user));
198 builder.append("/albumid/");
199 builder.append(album.id);
200 builder.append(BASE_QUERY_STRING);
201 builder.append("&kind=photo");
204 synchronized (mOperation) {
205 GDataClient.Operation operation = mOperation;
206 operation.inOutEtag = album.photosEtag;
207 boolean retry = false;
211 synchronized (mClient) {
212 mClient.get(builder.toString(), operation);
214 switch (operation.outStatus) {
215 case HttpStatus.SC_OK:
217 case HttpStatus.SC_NOT_MODIFIED:
218 return RESULT_NOT_MODIFIED;
219 case HttpStatus.SC_FORBIDDEN:
220 case HttpStatus.SC_UNAUTHORIZED:
221 // We need to reset the authtoken and retry only once.
224 accountManager.invalidateAuthToken(PicasaService.SERVICE_NAME, mAuth.authToken);
226 if (numRetries == 0) {
227 ++syncResult.stats.numAuthExceptions;
231 Log.e(TAG, "getAlbumPhotos: " + builder.toString() + ", unexpected status code " + operation.outStatus);
232 ++syncResult.stats.numIoExceptions;
236 } while (retry && numRetries >= 0);
238 // Store the new ETag for the album/photos feed.
239 album.photosEtag = operation.inOutEtag;
241 // Parse the response.
242 synchronized (mParser) {
243 GDataParser parser = mParser;
244 parser.setEntry(mPhotoInstance);
245 parser.setHandler(handler);
246 Xml.parse(operation.outBody, Xml.Encoding.UTF_8, parser);
250 } catch (IOException e) {
251 Log.e(TAG, "getAlbumPhotos: " + e);
252 ++syncResult.stats.numIoExceptions;
254 } catch (SAXException e) {
255 Log.e(TAG, "getAlbumPhotos: " + e);
256 ++syncResult.stats.numParseExceptions;
262 public int deleteEntry(String editUri) {
264 synchronized (mOperation) {
265 GDataClient.Operation operation = mOperation;
266 operation.inOutEtag = null;
267 synchronized (mClient) {
268 mClient.delete(editUri, operation);
270 if (operation.outStatus == 200) {
273 Log.e(TAG, "deleteEntry: failed with status code " + operation.outStatus);
276 } catch (IOException e) {
277 Log.e(TAG, "deleteEntry: " + e);
283 * Column names shared by multiple entry kinds.
285 public static class Columns {
286 public static final String _ID = "_id";
287 public static final String SYNC_ACCOUNT = "sync_account";
288 public static final String EDIT_URI = "edit_uri";
289 public static final String TITLE = "title";
290 public static final String SUMMARY = "summary";
291 public static final String DATE_PUBLISHED = "date_published";
292 public static final String DATE_UPDATED = "date_updated";
293 public static final String DATE_EDITED = "date_edited";
294 public static final String THUMBNAIL_URL = "thumbnail_url";
295 public static final String HTML_PAGE_URL = "html_page_url";