// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2009-2011 Google, All Rights reserved
// Copyright 2011-2012 MIT, All rights reserved
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0
package com.google.appinventor.components.runtime.util;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.DefaultHttpClient;
import java.io.IOException;
/**
*
*
* Helper to manage ClientLogin authentications tokens.
* This interacts with the Google Account manager to add the
* client login specific Authentication headers on each http request.
* (Inspired by chenplim's "comm" package)
*
*/
public class ClientLoginHelper implements IClientLoginHelper {
private static final String LOG_TAG = "ClientLoginHelper";
private static final String ACCOUNT_TYPE = "com.google";
private static final String AUTHORIZATION_HEADER_PREFIX = "GoogleLogin auth=";
private String service;
private HttpClient client;
private Activity activity;
private AccountManager accountManager;
private AccountChooser accountChooser;
private String authToken;
private boolean initialized = false;
/**
* Create one of these for each HttpClient needing clientlogin authentication.
* @param activity An activity that can be used for user interaction.
* @param service The application service class (e.g. "fusiontables").
* @param prompt The user prompt (if needed) to choose an account.
* @param client The HttpClient to use (or null for a default one).
*/
public ClientLoginHelper(Activity activity, String service, String prompt, HttpClient client) {
this.service = service;
this.client = (client == null) ? new DefaultHttpClient() : client;
this.activity = activity;
this.accountManager = AccountManager.get(activity);
this.accountChooser = new AccountChooser(activity, service, prompt, service);
}
/**
* Initialize the token, prompting the user for an account as needed.
* This can block waiting for user action, so it can't be on the UI thread
*/
private void initialize() throws ClientProtocolException {
if (!initialized) {
Log.i(LOG_TAG, "initializing");
if (isUiThread()) {
throw new IllegalArgumentException("Can't initialize login helper from UI thread");
}
authToken = getAuthToken();
initialized = true;
}
}
private boolean isUiThread() {
return Looper.getMainLooper().getThread().equals(Thread.currentThread());
}
/**
* Wraps an HttpClient.execute() to manage the authorization headers.
* This will add the proper Authorization header, and retry if the
* auth token has expired.
*/
@Override
public HttpResponse execute(HttpUriRequest request)
throws ClientProtocolException, IOException {
initialize();
addGoogleAuthHeader(request, authToken);
HttpResponse response = client.execute(request);
if (response.getStatusLine().getStatusCode() == 401) {
Log.i(LOG_TAG, "Invalid token: " + authToken);
accountManager.invalidateAuthToken(ACCOUNT_TYPE, authToken);
authToken = getAuthToken();
removeGoogleAuthHeaders(request);
addGoogleAuthHeader(request, authToken);
Log.i(LOG_TAG, "new token: " + authToken);
response = client.execute(request);
}
return response;
}
/**
* Forget about the account the user chose.
* The AccountChooser remembers (in shared prefs) the
* chosen account. Call this if you want to change the account
* this service is associated with.
*/
@Override
public void forgetAccountName() {
accountChooser.forgetAccountName();
}
private static void addGoogleAuthHeader(HttpUriRequest request, String token) {
if (token != null) {
Log.i(LOG_TAG, "adding auth token token: " + token);
request.addHeader("Authorization", AUTHORIZATION_HEADER_PREFIX + token);
}
}
private static void removeGoogleAuthHeaders(HttpUriRequest request) {
for (Header header : request.getAllHeaders()) {
if (header.getName().equalsIgnoreCase("Authorization") &&
header.getValue().startsWith(AUTHORIZATION_HEADER_PREFIX)) {
Log.i(LOG_TAG, "Removing header:" + header);
request.removeHeader(header);
}
}
}
/**
* Uses Google Account Manager to retrieve auth token that can
* be used to access various Google APIs -- e.g., the Google Voice api.
*/
public String getAuthToken() throws ClientProtocolException {
Account account = accountChooser.findAccount();
if (account != null) {
AccountManagerFuture<Bundle> future;
future = accountManager.getAuthToken(account, service, null, activity, null, null);
Log.i(LOG_TAG, "Have account, auth token: " + future);
Bundle result;
try {
result = future.getResult();
return result.getString(AccountManager.KEY_AUTHTOKEN);
} catch (AuthenticatorException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (OperationCanceledException e) {
e.printStackTrace();
}
}
throw new ClientProtocolException("Can't get valid authentication token");
}
}