package se.leiflandia.lroi.auth;
import android.accounts.AbstractAccountAuthenticator;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.accounts.NetworkErrorException;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import retrofit.RetrofitError;
import se.leiflandia.lroi.auth.model.AccessToken;
import se.leiflandia.lroi.auth.model.ClientCredentials;
import se.leiflandia.lroi.auth.model.RefreshTokenCredentials;
import se.leiflandia.lroi.network.AuthApi;
import se.leiflandia.lroi.ui.AbstractLoginActivity;
import se.leiflandia.lroi.utils.AuthUtils;
import se.leiflandia.lroi.utils.BundleUtils;
public class AccountAuthenticator extends AbstractAccountAuthenticator {
private static final String TAG = AccountAuthenticator.class.getSimpleName();
private Context context;
private AuthApi api;
private Class<? extends AbstractLoginActivity> loginActivity;
private String authtokenType;
private ClientCredentials clientCredentials;
private String accountType;
public AccountAuthenticator(Context context, AuthApi api, Class<? extends AbstractLoginActivity> loginActivity,
String authtokenType, ClientCredentials clientCredentials, String accountType) {
super(context);
this.context = context;
this.api = api;
this.loginActivity = loginActivity;
this.authtokenType = authtokenType;
this.clientCredentials = clientCredentials;
this.accountType = accountType;
}
@Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
return BundleUtils.createBundle(
AccountManager.KEY_INTENT,
AbstractLoginActivity.createIntent(context, loginActivity, response, accountType, authTokenType));
}
@Override
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
return null;
}
@Override
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
return null;
}
@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
Log.d(TAG, "getAuthToken() account=" + account.name + " type=" + account.type);
// If the caller requested an authToken type we don't support, then return an error
if (!TextUtils.equals(authTokenType, authtokenType)) {
Log.w(TAG, "Invalid authtoken type " + authTokenType);
return BundleUtils.createBundle(AccountManager.KEY_ERROR_MESSAGE, "Invalid auth token type.");
}
// Extract the username and password from the Account Manager, and ask the server for an
// appropriate AuthToken.
final AccountManager am = AccountManager.get(context);
// Refresh token stored as password.
final String refreshToken = am.getPassword(account);
if (refreshToken != null) {
Log.v(TAG, "Trying to refresh access token using refresh token.");
try {
final AccessToken token = api.refreshAccessToken(
new RefreshTokenCredentials(clientCredentials, refreshToken, "refresh_token"));
if (!TextUtils.isEmpty(token.getAccessToken())) {
Log.v(TAG, "Retrieved new access token.");
am.setPassword(account, token.getRefreshToken());
return createAuthTokenBundle(account.name, accountType, token.getAccessToken());
}
} catch (RetrofitError e) {
if (e.getKind() == RetrofitError.Kind.NETWORK) {
Log.e(TAG, "Failed to refresh access token because of a network error.", e);
throw e;
} else {
Log.e(TAG, "Failed to refresh access token.", e);
// Remove account
AuthUtils.removeActiveAccount(context, accountType);
}
} catch (Exception e) {
Log.e(TAG, "Failed to refresh access token.", e);
}
}
// If we get here, then we couldn't access the user's refresh token or we failed to refresh
// the access token. So we re-prompt them for their credentials.
final Intent intent = AbstractLoginActivity.createIntent(context, loginActivity, response, account.name, accountType, authTokenType);
return BundleUtils.createBundle(AccountManager.KEY_INTENT, intent);
}
private static Bundle createAuthTokenBundle(final String accountName, final String accountType,
final String accessToken) {
final Bundle result = new Bundle();
result.putString(AccountManager.KEY_ACCOUNT_NAME, accountName);
result.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType);
result.putString(AccountManager.KEY_AUTHTOKEN, accessToken);
return result;
}
@Override
public String getAuthTokenLabel(String authTokenType) {
return null;
}
@Override
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
return null;
}
@Override
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
// This call is used to query whether the Authenticator supports specific features. We don't
// expect to get called, so we always return false for any queries.
return BundleUtils.createBundle(AccountManager.KEY_BOOLEAN_RESULT, false);
}
}