/* * Copyright (C) 2012 Pixmob (http://github.com/pixmob) * * 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 org.pixmob.httpclient; import java.io.IOException; import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.AccountManagerFuture; import android.accounts.AuthenticatorException; import android.accounts.OperationCanceledException; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.Bundle; /** * Abstract Http authenticator using {@link AccountManager} to get * authentication tokens. Subclass this handler to get easy access to user * authentication token from {@link AccountManager}, by calling * {@link #generateAuthToken()}. This token allows an application to * authenticate using user credential without requesting for the user password. * @see #generateAuthToken() * @author Pixmob */ public abstract class AbstractAccountAuthenticator extends HttpRequestHandler { private final Context context; private final Account account; public AbstractAccountAuthenticator(final Context context, final Account account) { if (context == null) { throw new IllegalArgumentException("Context is required"); } if (account == null) { throw new IllegalArgumentException("Account is required"); } this.context = context; this.account = account; } public final Context getContext() { return context; } public final Account getAccount() { return account; } /** * Generate an authentication token. The user must grant credential access * when an application is using it for the first time. In this case, * {@link UserInteractionRequiredException} is thrown, the * {@link UserInteractionRequiredException#getUserIntent()} must be used * with <code>startActivityForResult</code> to start a system activity, in * order to get access to user credential. The user is free to deny * credential access. If credential access is granted, the next call to this * method should not throw any error. * @see UserInteractionRequiredException#getUserIntent() * @throws UserInteractionRequiredException * if user interaction is required in order to perform * authentication * @throws HttpClientException * if authentication failed (network error, bad credentials, * etc...) */ protected final String generateAuthToken(String authTokenType) throws HttpClientException { // Get an authentication token from the AccountManager: // this call is asynchronous, as the user may not respond immediately. final AccountManager am = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE); final AccountManagerFuture<Bundle> authResultFuture; // The AccountManager API for authentication token is different before // Ice Cream Sandwich. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { authResultFuture = GetTokenLegacy.INSTANCE.get(am, account, authTokenType); } else { authResultFuture = GetTokenICS.INSTANCE.get(am, account, authTokenType); } final Bundle authResult; try { authResult = authResultFuture.getResult(); } catch (OperationCanceledException e) { throw new HttpClientException("Authentication failed: canceled by user", e); } catch (AuthenticatorException e) { throw new HttpClientException("Authentication failed", e); } catch (IOException e) { throw new HttpClientException("Authentication failed: network error", e); } if (authResult == null) { throw new HttpClientException("Authentication failed"); } final String authToken = authResult.getString(AccountManager.KEY_AUTHTOKEN); if (authToken == null) { // No authentication token found: // the user must allow this application to use his account. final Intent authPermIntent = (Intent) authResult.get(AccountManager.KEY_INTENT); int flags = authPermIntent.getFlags(); flags &= ~Intent.FLAG_ACTIVITY_NEW_TASK; authPermIntent.setFlags(flags); // The request is aborted: the application should retry later. throw new UserInteractionRequiredException(authPermIntent); } return authToken; } private static class GetTokenLegacy { public static final GetTokenLegacy INSTANCE = new GetTokenLegacy(); @SuppressWarnings("deprecation") public AccountManagerFuture<Bundle> get(AccountManager am, Account account, String authTokenType) { return am.getAuthToken(account, authTokenType, false, null, null); } } private static class GetTokenICS { public static final GetTokenICS INSTANCE = new GetTokenICS(); @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public AccountManagerFuture<Bundle> get(AccountManager am, Account account, String authTokenType) { return am.getAuthToken(account, authTokenType, null, false, null, null); } } }