2010-08-28

AccountManager: dem Geheimnis auf der Spur

Lange Zeit hatten Anwendungsentwickler das Problem, vom Benutzer Dinge abfragen zu müssen, die dem System (seinem Handy) eigentlich bekannt sind: zum Beispiel die Daten zu seinem Google Konto. Schon seit Eclair gibt es deshalb die Klasse AccountManager, die als zentrale Speicherstelle der vom Benutzer angemeldeten Onlinekonten fungiert. Die Idee ist, sich vom AccountManager einfach ein so genanntes Token geben zu lassen, um dann mit diesem Token auf einen Webservice zuzugreifen. Damit müsste es dann eigentlich ganz einfach sein, zum Beispiel mit Google Contacts oder Google Calendar zu reden. …oder…?

Wer sich mal den Spaß gemacht hat, das Web daraufhin abzusuchen, wird wahrscheinlich zu dem Schluss kommen, dass es so ohne weiteres offenbar doch nicht geht. …geht ja wohl! Wie üblich bei mir, zunächst der Quelltext:

package com.thomaskuenneth;

import java.io.InputStream;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

public class TKSimpleTasks extends Activity {

  private static final String TAG = TKSimpleTasks.class.getSimpleName();

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    AccountManager am = AccountManager.get(this);
    Account[] accounts = am.getAccountsByType("com.google");
    if (accounts.length < 1) {
      finish();
    }
    String authTokenType = "cl";
    Bundle options = null;
    am.getAuthToken(accounts[0], authTokenType, options, this,
        new AccountManagerCallback<Bundle>() {

          public void run(AccountManagerFuture<Bundle> arg0) {
            try {
              Bundle b = arg0.getResult();
              String token = b
                  .getString(AccountManager.KEY_AUTHTOKEN);
              Log.d(TAG, token);

              HttpGet get = new HttpGet(
                  "https://www.google.com/calendar/feeds/default/owncalendars/full");
              get.addHeader("Authorization", "GoogleLogin auth="
                  + token);

              HttpClient httpclient = new DefaultHttpClient();
              HttpResponse r = httpclient.execute(get);
              InputStream is = r.getEntity().getContent();
              StringBuilder sb = new StringBuilder();
              int ch;
              while ((ch = is.read()) != -1) {
                sb.append((char) ch);
              }

              ((TextView) findViewById(R.id.tv)).setText(sb
                  .toString());

            } catch (Throwable thr) {
              Log.e(TAG, thr.getMessage(), thr);
            }
          }
        }, null);
  }
}

Zunächst holt man sich eine Instanz des AccountManagers:

AccountManager am = AccountManager.get(this);

Anschließend sucht man nach geeigneten Konten:

Account[] accounts = am.getAccountsByType("com.google");

Das in der Einleitung angesprochene Token erhalten Sie durch Aufruf der Methode getAuthToken. Der eigentlich spannende Teil diesbezüglich steht hier:

Bundle b = arg0.getResult();
String token = b.getString(AccountManager.KEY_AUTHTOKEN);

Sie werden jetzt vielleicht sagen, dass Sie ähnliche Quelltextfragmente recht oft finden. Das stimmt. Aber ich behaupte, kaum jemand schreibt, was es mit dem ominösen authTokenType auf sich hat. Damit steuern Sie, auf welchen Teil oder Bereich eines Google Kontos Sie Zugriff haben möchten. Das Beispiel liest den Kalender aus. Das eigentliche Lesen ist hier nur skizzenhaft ausprogrammiert. Ganz wichtig – und das werden Sie sehr selten finden – ist das Setzen des Tokens im Header des HTTP-GETs.

Bitte beachten Sie noch, dass Ihre Anwendung die Rechte GET_ACCOUNTS, USE_CREDENTIALS und INTERNET braucht. Haben Sie Fragen, Anregungen oder Kommentare? Schreiben Sie mir.

4 comments:

  1. Der Codeschnipsel ist echt gut. Ich habe das Gleiche auch mit dem Google Reader Feed ausprobiert, aber da kann ich mich nicht anmelden (401).

    Haben Sie dafür eine Lösung?

    Danke im Voraus.

    MfG
    FBone

    ReplyDelete
  2. Anstelle des "cl" im authTokenType müssen Sie meiner Meinung nach "reader" eintragen. In der Liste unter http://code.google.com/intl/de-DE/apis/gdata/faq.html#clientlogin steht das zwar nicht drin, aber einen Versuch (ohne Gewähr!!!) ist es wert.

    ReplyDelete
  3. Danke! Ich werde es ausprobieren!

    Gruß
    FBone

    ReplyDelete
  4. Jetzt habe ich noch ein Problem. Wenn ich den StringBuilder auslese, ist dieser nicht UTF-8 codiert. Die ganzen Sonderzeichen sind zu komischen Zeichen geworden.

    Haben Sie dafür vielleicht eine Lösung?

    Gruß
    FBone

    ReplyDelete