Tuesday, September 11, 2012

Connect to Picasa album from Android using OAuth2

In this article you will learn how to connect to Picasa service using OAuth2 protocol from Android. This example uses only pure Android API, i.e. no extra jars of libraries are needed.

If you plan to connect to the Picasa albums from your Android application, you probably know, that there is a relatively big number of different "samples" and "guides" which encourages you to use "this protocol" and "that library" saying in turn that this and that is already deprecated and should not be used anymore. Moreover majority of those samples are about different (tasks) services and therefore can not work with Picasa service without adjustments, which are not clear. I have been googling around for about four days as I tried to figure out, what is the proper way of working with Picasa albums, downloading tons of sources, including bags of jars to my "simplesample" project.

Finally I have figured out, that working with Picasa is very easy from Android if you know how to do It.

All we have to do is :

  • Register our application on the Api console https://code.google.com/apis/console/ to get access to the service. Note, that Picasa service is not listed in possible services. It does not matter as you can have 0 active service to work with picasa.
  • Take an OAuth2 authToken,from AccountManager
  • Sign your HttpURLConnection with the authToken


Data that you obtain from Picasa service are in the Atom XML format and therefore there are very simple to parse. For more detailed reference about how to query Picasa see https://developers.google.com/picasa-web/docs/2.0/developers_guide_protocol .

Here goes an activity example. The activity will list albums titles to the LogCat window in Eclipse:

//FIXME This class does not deal with situation when there is not any user account in the device
public class ActivityGallery extends Activity
  implements AccountManagerCallback<Bundle> {
  private final String LOGTAG = getClass().getName();
  /**This is what you have got from the api console https://code.google.com/apis/console/**/
  private final String CLIENT_ID = "123blahblahblah456.com";
  /**
   * Service identification. Note the prefix oauth2 - it is necessary
   * See https://developers.google.com/gdata/faq?hl=cs#AuthScopes
   **/
  private final String AUTH_TOKEN_TYPE = "oauth2:http://picasaweb.google.com/data/";
  /** Type of accounts that we are interested in **/
  private final String ACCOUNT_TYPE = "com.google";
  /** Keys for a settings items - not auth-related stuff ;-) **/
  static final String PREFKEY_ACCOUNT_NAME = "pref1_name";
  static final String PREFKEY_AUTH_TOKEN = "pref2_token";
  private SharedPreferences settings;
  private AccountManager aManager;
  private String accountName;
  private String authToken;
  private HttpURLConnection connection;


  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Inspect preferences for saved values and get AccountManager
    settings = getPreferences(Activity.MODE_PRIVATE);
    // accountName = settings.getString(PREFKEY_ACCOUNT_NAME, null);
    // authToken = settings.getString(PREFKEY_AUTH_TOKEN, null);
    aManager = AccountManager.get(this);
    dance();
  }

  /**
   * Do the authentication dance.
   * At first try to use an account name and an authToken from the
   * SharedPreferences
   * If the first fails, try to get authToken for an accountName from
   * SharedPreferences
   * If the second fails, try to choose an account and get authToken for it.
   */
  public void dance() {
    Log.v(LOGTAG, "dance()");
    // We have got everything
    if (accountName != null && authToken != null) {
      listAlbums(true);
    } else {
      // We have to do more to obtain an authToken
      Account account = null;
      // We have got some account name, let's verify if it is still valid
      if (accountName != null) {
        Account[] accounts = aManager.getAccountsByType(ACCOUNT_TYPE);
        for (Account acc : accounts) {
          if (acc.name.equals(accountName)) {
            account = acc;
            break;
          }
        }
      }
      Bundle options=new Bundle();
      if (account != null) {
        // Well, we don't have an authToken, but we have a valid account
        aManager.getAuthToken(account, AUTH_TOKEN_TYPE, options, this, this,
            null);
      } else {
        // Hmmm, we have to choose an account to use
        Log.v(LOGTAG, "chooseAccount()");
        aManager.getAuthTokenByFeatures(ACCOUNT_TYPE, AUTH_TOKEN_TYPE, null,
            this, options, null, this, null);
      }
    }
  }
  /**
   * Callback method from getAuthTokenByFeatures() and getAuthToken(). All we
   * have to do
   * is remember an authToken and an accountName and then begin to work.
   * 
   * @param future
   */
  @Override
  public void run(AccountManagerFuture<Bundle> future) {
    Log.v(LOGTAG, "AccountManagerCallback.run()");
    try {
      Bundle bundle = future.getResult();
      if (bundle.containsKey(AccountManager.KEY_AUTHTOKEN)) {
        SharedPreferences.Editor editor = settings.edit();
        authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
        Log.v(LOGTAG, "setAuthToken()" + authToken);
        editor.putString(PREFKEY_AUTH_TOKEN, authToken);
        accountName = bundle.getString(AccountManager.KEY_ACCOUNT_NAME);
        Log.v(LOGTAG, "setAccountName()" + accountName);
        editor.putString(PREFKEY_ACCOUNT_NAME, accountName);
        editor.commit();
        listAlbums(true);
      }
    } catch (AuthenticatorException _e) {
      _e.printStackTrace();
      // TODO if the authenticator failed to respond
    } catch (OperationCanceledException _e) {
      _e.printStackTrace();
      // TODO user cancelled the operation
    } catch (IOException _e) {
      _e.printStackTrace();
      // TODO if I/O problem creating a new auth token, usually because of
      // network trouble
    }
  }

  public void listAlbums(boolean _ready) {
    URL url = null;
    try {
      url = new URL("https://picasaweb.google.com/data/feed/api/user/default");
    } catch (MalformedURLException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    try {
      connection = (HttpURLConnection) url.openConnection();
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    connection.addRequestProperty("X-GData-Client", CLIENT_ID);//This may be is not necessary
    connection.setRequestProperty("GData-Version", "2");
    connection.setRequestProperty("Authorization", "OAuth " + authToken);
    try {
      Log.v(LOGTAG, "Response" + connection.getResponseCode() + " ("
          + connection.getResponseMessage() + ")");
      if(connection.getResponseCode()==401 || connection.getResponseCode()==403){
        aManager.invalidateAuthToken(ACCOUNT_TYPE, authToken);
        authToken=null;
        //Dance again to refresh the authToken
        //- it should be treated more carefully to prevent an eternal dance
        dance();
        return;
      }
      //List user's albums
      DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
      try {
        DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
        Document doc = dBuilder.parse(connection.getInputStream());
        Element root = doc.getDocumentElement();
        NodeList nodes = root.getElementsByTagName("entry");
        for (int i = 0; i < nodes.getLength(); i++) {
          Element element = (Element)nodes.item(i);
          Element title=(Element)element.getElementsByTagName("title").item(0);
          Log.v(LOGTAG, title.getChildNodes().item(0).getNodeValue()+"");
        }
        
      } catch (ParserConfigurationException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      } catch (SAXException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }

    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}

Note, that you will need following permissions declared in yout manifest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

Hope this will work for you.

7 comments:

  1. wow this is very useful for me
    thank you

    ReplyDelete
  2. Been googling for days too with my own bags of jars! Thanks for this. Will give it a try. I'm looking to do uploads. Will reply back with what I find

    ReplyDelete
  3. sorry for being dumb

    but how could i use this?
    where will i place the account name and password of the picasa account?

    how would u get the data?
    what does the method dance really do?

    again sorry for being dumb

    ReplyDelete
  4. Hello,

    Can you make an example for uploading to Piscasa?

    ReplyDelete
  5. https://picasaweb.google.com/116905256675614721023/TripToItaly?authuser=0&feat=directlink

    just created this album with android. Thanks for the help.

    ReplyDelete
  6. Hi,
    I am using this code In my application,If run my app it will asking select google account after selection window disappear, nothing shows in my log cat.any help,thanks in andvance.

    ReplyDelete