| /* |
| * Copyright (C) 2009 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.cooliris.picasa; |
| |
| import java.io.IOException; |
| import java.net.SocketException; |
| import java.util.ArrayList; |
| |
| import org.apache.http.HttpStatus; |
| import org.xml.sax.SAXException; |
| |
| import android.accounts.Account; |
| import android.accounts.AccountManager; |
| import android.accounts.AuthenticatorException; |
| import android.accounts.OperationCanceledException; |
| import android.app.Activity; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.SyncResult; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.provider.Settings; |
| import android.util.Log; |
| import android.util.Xml; |
| |
| public final class PicasaApi { |
| public static final int RESULT_OK = 0; |
| public static final int RESULT_NOT_MODIFIED = 1; |
| public static final int RESULT_ERROR = 2; |
| |
| private static final String TAG = "PicasaAPI"; |
| private static final String SETTINGS_PICASA_GDATA_BASE_URL_KEY = "picasa_gdata_base_url"; |
| private static final String DEFAULT_BASE_URL = "https://picasaweb.google.com/data/feed/api/"; |
| private static final String BASE_QUERY_STRING; |
| |
| static { |
| // Build the base query string using screen dimensions. |
| final StringBuilder query = new StringBuilder("?imgmax=1024&max-results=1000&thumbsize="); |
| final String thumbnailSize = "144u,"; |
| final String screennailSize = "1024u"; |
| query.append(thumbnailSize); |
| query.append(screennailSize); |
| BASE_QUERY_STRING = query.toString() + "&visibility=visible"; |
| } |
| |
| private final ContentResolver mContentResolver; |
| private final GDataClient mClient; |
| private final GDataClient.Operation mOperation = new GDataClient.Operation(); |
| private final GDataParser mParser = new GDataParser(); |
| private final AlbumEntry mAlbumInstance = new AlbumEntry(); |
| private final PhotoEntry mPhotoInstance = new PhotoEntry(); |
| private AuthAccount mAuth; |
| |
| public static final class AuthAccount { |
| public final String user; |
| public final String authToken; |
| public final Account account; |
| |
| public AuthAccount(String user, String authToken, Account account) { |
| this.user = user; |
| this.authToken = authToken; |
| this.account = account; |
| } |
| } |
| |
| public static Account[] getAccounts(Context context) { |
| // Return the list of accounts supporting the Picasa GData service. |
| AccountManager accountManager = AccountManager.get(context); |
| Account[] accounts = {}; |
| try { |
| accounts = accountManager.getAccountsByTypeAndFeatures(PicasaService.ACCOUNT_TYPE, |
| new String[] { PicasaService.FEATURE_SERVICE_NAME }, null, null).getResult(); |
| } catch (OperationCanceledException e) { |
| } catch (AuthenticatorException e) { |
| } catch (IOException e) { |
| } catch (Exception e) { |
| ; |
| } |
| return accounts; |
| } |
| |
| public static AuthAccount[] getAuthenticatedAccounts(Context context) { |
| AccountManager accountManager = AccountManager.get(context); |
| Account[] accounts = getAccounts(context); |
| if (accounts == null) |
| accounts = new Account[0]; |
| int numAccounts = accounts.length; |
| |
| ArrayList<AuthAccount> authAccounts = new ArrayList<AuthAccount>(numAccounts); |
| for (int i = 0; i != numAccounts; ++i) { |
| Account account = accounts[i]; |
| String authToken; |
| try { |
| // Get the token without user interaction. |
| authToken = accountManager.blockingGetAuthToken(account, PicasaService.SERVICE_NAME, true); |
| |
| // TODO: Remove this once the build is signed by Google, since |
| // we will always have permission. |
| // This code requests permission from the user explicitly. |
| if (context instanceof Activity) { |
| Bundle bundle = accountManager.getAuthToken(account, PicasaService.SERVICE_NAME, null, (Activity) context, |
| null, null).getResult(); |
| authToken = bundle.getString("authtoken"); |
| PicasaService.requestSync(context, PicasaService.TYPE_USERS_ALBUMS, -1); |
| } |
| |
| // Add the account information to the list of accounts. |
| if (authToken != null) { |
| String username = canonicalizeUsername(account.name); |
| authAccounts.add(new AuthAccount(username, authToken, account)); |
| } |
| } catch (OperationCanceledException e) { |
| } catch (IOException e) { |
| } catch (AuthenticatorException e) { |
| } catch (Exception e) { |
| ; |
| } |
| } |
| AuthAccount[] authArray = new AuthAccount[authAccounts.size()]; |
| authAccounts.toArray(authArray); |
| return authArray; |
| } |
| |
| /** |
| * Returns a canonical username for a Gmail account. Lowercases the username and |
| * strips off a "gmail.com" or "googlemail.com" domain, but leaves other domains alone. |
| * |
| * e.g., Passing in "User@gmail.com: will return "user". |
| * |
| * @param username The username to be canonicalized. |
| * @return The username, lowercased and possibly stripped of its domain if a "gmail.com" or |
| * "googlemail.com" domain. |
| */ |
| public static String canonicalizeUsername(String username) { |
| username = username.toLowerCase(); |
| if (username.contains("@gmail.") || username.contains("@googlemail.")) { |
| // Strip the domain from GMail accounts for |
| // canonicalization. TODO: is there an official way? |
| username = username.substring(0, username.indexOf('@')); |
| } |
| return username; |
| } |
| |
| public PicasaApi(ContentResolver cr) { |
| mContentResolver = cr; |
| mClient = new GDataClient(); |
| } |
| |
| public void setAuth(AuthAccount auth) { |
| mAuth = auth; |
| synchronized (mClient) { |
| mClient.setAuthToken(auth.authToken); |
| } |
| } |
| |
| public int getAlbums(AccountManager accountManager, SyncResult syncResult, UserEntry user, GDataParser.EntryHandler handler) { |
| // Construct the query URL for user albums. |
| String baseUrl = Settings.Secure.getString(mContentResolver, |
| SETTINGS_PICASA_GDATA_BASE_URL_KEY); |
| StringBuilder builder = new StringBuilder(baseUrl != null ? baseUrl : DEFAULT_BASE_URL); |
| builder.append("user/"); |
| builder.append(Uri.encode(mAuth.user)); |
| builder.append(BASE_QUERY_STRING); |
| builder.append("&kind=album"); |
| try { |
| // Send the request. |
| synchronized (mOperation) { |
| GDataClient.Operation operation = mOperation; |
| operation.inOutEtag = user.albumsEtag; |
| boolean retry = false; |
| int numRetries = 1; |
| do { |
| retry = false; |
| synchronized (mClient) { |
| mClient.get(builder.toString(), operation); |
| } |
| switch (operation.outStatus) { |
| case HttpStatus.SC_OK: |
| break; |
| case HttpStatus.SC_NOT_MODIFIED: |
| return RESULT_NOT_MODIFIED; |
| case HttpStatus.SC_FORBIDDEN: |
| case HttpStatus.SC_UNAUTHORIZED: |
| if (!retry) { |
| accountManager.invalidateAuthToken(PicasaService.ACCOUNT_TYPE, mAuth.authToken); |
| retry = true; |
| } |
| if (numRetries == 0) { |
| ++syncResult.stats.numAuthExceptions; |
| } |
| default: |
| Log.i(TAG, "getAlbums uri " + builder.toString()); |
| Log.e(TAG, "getAlbums: unexpected status code " + operation.outStatus + " data: " |
| + operation.outBody.toString()); |
| ++syncResult.stats.numIoExceptions; |
| return RESULT_ERROR; |
| } |
| --numRetries; |
| } while (retry && numRetries >= 0); |
| |
| // Store the new ETag for the user/albums feed. |
| user.albumsEtag = operation.inOutEtag; |
| |
| // Parse the response. |
| synchronized (mParser) { |
| GDataParser parser = mParser; |
| parser.setEntry(mAlbumInstance); |
| parser.setHandler(handler); |
| try { |
| Xml.parse(operation.outBody, Xml.Encoding.UTF_8, parser); |
| } catch (SocketException e) { |
| Log.e(TAG, "getAlbumPhotos: " + e); |
| ++syncResult.stats.numIoExceptions; |
| e.printStackTrace(); |
| return RESULT_ERROR; |
| } |
| } |
| } |
| return RESULT_OK; |
| } catch (IOException e) { |
| Log.e(TAG, "getAlbums: " + e); |
| ++syncResult.stats.numIoExceptions; |
| } catch (SAXException e) { |
| Log.e(TAG, "getAlbums: " + e); |
| ++syncResult.stats.numParseExceptions; |
| } |
| return RESULT_ERROR; |
| } |
| |
| public int getAlbumPhotos(AccountManager accountManager, SyncResult syncResult, AlbumEntry album, |
| GDataParser.EntryHandler handler) { |
| // Construct the query URL for user albums. |
| String baseUrl = Settings.Secure.getString(mContentResolver, |
| SETTINGS_PICASA_GDATA_BASE_URL_KEY); |
| StringBuilder builder = new StringBuilder(baseUrl != null ? baseUrl : DEFAULT_BASE_URL); |
| builder.append("user/"); |
| builder.append(Uri.encode(mAuth.user)); |
| builder.append("/albumid/"); |
| builder.append(album.id); |
| builder.append(BASE_QUERY_STRING); |
| builder.append("&kind=photo"); |
| try { |
| // Send the request. |
| synchronized (mOperation) { |
| GDataClient.Operation operation = mOperation; |
| operation.inOutEtag = album.photosEtag; |
| boolean retry = false; |
| int numRetries = 1; |
| do { |
| retry = false; |
| synchronized (mClient) { |
| mClient.get(builder.toString(), operation); |
| } |
| switch (operation.outStatus) { |
| case HttpStatus.SC_OK: |
| break; |
| case HttpStatus.SC_NOT_MODIFIED: |
| return RESULT_NOT_MODIFIED; |
| case HttpStatus.SC_FORBIDDEN: |
| case HttpStatus.SC_UNAUTHORIZED: |
| // We need to reset the authtoken and retry only once. |
| if (!retry) { |
| retry = true; |
| accountManager.invalidateAuthToken(PicasaService.SERVICE_NAME, mAuth.authToken); |
| } |
| if (numRetries == 0) { |
| ++syncResult.stats.numAuthExceptions; |
| } |
| break; |
| default: |
| Log.e(TAG, "getAlbumPhotos: " + builder.toString() + ", unexpected status code " + operation.outStatus); |
| ++syncResult.stats.numIoExceptions; |
| return RESULT_ERROR; |
| } |
| --numRetries; |
| } while (retry && numRetries >= 0); |
| |
| // Store the new ETag for the album/photos feed. |
| album.photosEtag = operation.inOutEtag; |
| |
| // Parse the response. |
| synchronized (mParser) { |
| GDataParser parser = mParser; |
| parser.setEntry(mPhotoInstance); |
| parser.setHandler(handler); |
| try { |
| Xml.parse(operation.outBody, Xml.Encoding.UTF_8, parser); |
| } catch (SocketException e) { |
| Log.e(TAG, "getAlbumPhotos: " + e); |
| ++syncResult.stats.numIoExceptions; |
| e.printStackTrace(); |
| return RESULT_ERROR; |
| } |
| } |
| } |
| return RESULT_OK; |
| } catch (IOException e) { |
| Log.e(TAG, "getAlbumPhotos: " + e); |
| ++syncResult.stats.numIoExceptions; |
| e.printStackTrace(); |
| } catch (SAXException e) { |
| Log.e(TAG, "getAlbumPhotos: " + e); |
| ++syncResult.stats.numParseExceptions; |
| e.printStackTrace(); |
| } |
| return RESULT_ERROR; |
| } |
| |
| public int deleteEntry(String editUri) { |
| try { |
| synchronized (mOperation) { |
| GDataClient.Operation operation = mOperation; |
| operation.inOutEtag = null; |
| synchronized (mClient) { |
| mClient.delete(editUri, operation); |
| } |
| if (operation.outStatus == 200) { |
| return RESULT_OK; |
| } else { |
| Log.e(TAG, "deleteEntry: failed with status code " + operation.outStatus); |
| } |
| } |
| } catch (IOException e) { |
| Log.e(TAG, "deleteEntry: " + e); |
| } |
| return RESULT_ERROR; |
| } |
| |
| /** |
| * Column names shared by multiple entry kinds. |
| */ |
| public static class Columns { |
| public static final String _ID = "_id"; |
| public static final String SYNC_ACCOUNT = "sync_account"; |
| public static final String EDIT_URI = "edit_uri"; |
| public static final String TITLE = "title"; |
| public static final String SUMMARY = "summary"; |
| public static final String DATE_PUBLISHED = "date_published"; |
| public static final String DATE_UPDATED = "date_updated"; |
| public static final String DATE_EDITED = "date_edited"; |
| public static final String THUMBNAIL_URL = "thumbnail_url"; |
| public static final String HTML_PAGE_URL = "html_page_url"; |
| } |
| } |