I use a SyncAdapter and an AccountAuthenticator in my app. When doing the sync stuff, I call AccountManager.blockingGetAuthToken to get an access token. I understand this method that way, that it starts my Log-in activity when it can not get a token (or in other words, when the getAuthToken methods returns an Intent to start the Activity).
But it just returns null, without launching the Activity.
This is the getAuthToken method from my authenticator.
#Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
// Extract the username and password from the Account Manager, and ask
// the server for an appropriate AuthToken.
final AccountManager am = AccountManager.get(mContext);
String authToken = am.peekAuthToken(account, authTokenType);
// Lets give another try to authenticate the user
if (TextUtils.isEmpty(authToken)) {
final String password = am.getPassword(account);
if (password != null) {
try {
authToken = APIHelper.getInstance().logIn(account.name, password);
} catch (IOException e) {
e.printStackTrace();
}
}
}
// If we get an authToken - we return it
if (!TextUtils.isEmpty(authToken)) {
// cache
am.setAuthToken(account, authTokenType, authToken);
final Bundle result = new Bundle();
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
return result;
}
// If we get here, then we couldn't access the user's password - so we
// need to re-prompt them for their credentials. We do that by creating
// an intent to display our AuthenticatorActivity.
final Intent intent = new Intent(mContext, AuthActivity.class);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
intent.putExtra(AuthActivity.ARG_ACCOUNT_TYPE, account.type);
intent.putExtra(AuthActivity.ARG_AUTH_TYPE, authTokenType);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
By the way, most of the code is from
this blog.
It seems the asker figured it out. For posterity, it works if you use this instead:
AccountManagerFuture<Bundle> resultFuture = accountManager.getAuthToken(
account,
AUTH_TOKEN_TYPE,
null,
activity,
null,
null
);
Bundle bundle = resultFuture.getResult();
return bundle.getString(AccountManager.KEY_AUTHTOKEN);
I guess blockingGetAuthToken() is unable to do this automatically because it lacks the activity parameter. And the documentation is incorrect.
Related
I'm implementing the android account authenticator, so far I can add accounts get a token etc.
The problem is when I change credential server side.
As I can't get notified from the server if credentials changed, my next API request will be denied as the token is not longer valid.
After getting a request denied there could be 2 reasons for it -> token expired or credentials no longer valid
When this happen I invalidate the token saved and call getAuthToken()
In my getAuthToken() I first attempt a request for a new token, if it gets denied means that credential are not ok anymore so I need to prompt login activity.
The problem is AccountAuthenticatorResponse.onError seems only able to log the error and that's it.
I tried to use AccountAuthenticatorResponse.onResult passing the bundle with the KEY_INTENT for login activity but it does't do anything.
Any thoughts?
#Override
public Bundle getAuthToken(final AccountAuthenticatorResponse authenticatorResponse, final Account account,
final String authTokenType, Bundle bundle) throws NetworkErrorException {
//Get the account manager to access the account details
final AccountManager accountManager = AccountManager.get(mContext);
String authToken = accountManager.peekAuthToken(account, authTokenType);
//If auth token is null then try to log in the user with the stored credentials
//It could be that previous token has expired
if (authToken == null) {
final String password = accountManager.getPassword(account);
final String clientID = accountManager.getUserData(account, CLIENT_ID_KEY);
final String apiSecret = accountManager.getUserData(account, API_SECRET_KEY);
final String serverUrl = accountManager.getUserData(account, SERVER_ADDRESS_KEY);
if (password != null && clientID != null && apiSecret != null && serverUrl != null) {
Logger.log(LOG_TAG, "Requesting new token...", Log.VERBOSE);
ApiRequestManager.getInstance(mContext)
.getToken(serverUrl, clientID, apiSecret, account.name, password,
new NetworkCallBack() {
#Override
public void tokenReceived(Token JsonToken) {
//Credentials still valid, token received
//Returning data back to the account authenticator
Bundle result = new Bundle();
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
result.putString(AccountManager.KEY_AUTHTOKEN, JsonToken.getAccess_token());
authenticatorResponse.onResult(result);
}
#Override
public void errorReceivingToken(VolleyError errorResponse) {
//If we are here with error 400 it only means credentials have changed
//I should prompt LogIn activity at this point
if (errorResponse.networkResponse.statusCode == 400) {
Bundle loginActivityBundle =
promptLoginActivity(authenticatorResponse, account.type, authTokenType, null);
// authenticatorResponse.onResult(loginActivityBundle);
authenticatorResponse.onError(errorResponse.networkResponse.statusCode, "error");
}
}
});
return null;
}
}
//If we got an authToken return the account and login info in a bundle
if (authToken != null) {
Bundle result = new Bundle();
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
return result;
}
//If we are here then means either we do not have an account signed
//or credentials are no longer valid -> prompt login procedure again
return promptLoginActivity(authenticatorResponse, account.type, authTokenType, null);
}
I found the solution.
When I invoke getAuthToken() i pass an accountManagerCallback which receives either success or fail, from here I can prompt logIn if it was a fail due to auth exception.
why we use Accountmanager.addAccount() when we can create account with all parameters with Accountmanager.addAccountExplicitly().
I googled and find out when we use Accountmanager.addAccount() it trigles AbstractAccountAuthenticator addAccount event but what is the point?
why we should using addAccount method?
UPDATED
we can create account in this way:
Account account = new Account(accountname, accountType);
mAccountManager.addAccountExplicitly(account, accountPassword, null);
I finally find out after many tries!
Accountmanager.addAccount()
and
Accountmanager.addAccountExplicitly() are very different methods!
when you call Accountmanager.addAccount() it's call a same method that in your AbstractAccountAuthenticator you can handle what happens. in other hand when user go to phone settings/account and select your custom account type and press "add an account" this method will call. so I handle account-type and many stuff and direct user to login/singup page.
public class MyAuthenticator extends AbstractAccountAuthenticator {
#Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
final Intent intent = new Intent(mContext, DirectLogin.class);
intent.putExtra(Constants.ARG_ACCOUNT_TYPE, accountType);
intent.putExtra(Constants.ARG_AUTH_TYPE, authTokenType);
intent.putExtra(Constants.ARG_IS_ADDING_NEW_ACCOUNT, true);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
then in my activiy user chose to create an account or sign in. after sing-in or sign-up user get tokens from server and action completes and finally I use Accountmanager.addAccountExplicitly() to add account.
String accountName = "name";
String accountPassword = "password";
final Account account = new Account(accountName, "account_type");
AccountManager mAccountManager = AccountManager.get(getBaseContext());
String authToken = intent.getStringExtra(AccountManager.KEY_AUTHTOKEN);
String refreshToken = intent.getStringExtra(AccountManager.KEY_USERDATA);
String authTokenType = AccountGeneral.ACCOUNT_TYPE;
mAccountManager.addAccountExplicitly(account, accountPassword, null);
mAccountManager.setAuthToken(account, authTokenType, authToken);
mAccountManager.setUserData(account, "refreshToken", refreshToken);
Accountmanager.addAccount() must be use to ask the user to create an account of some type. The user have to approve and interact with the device so that the account get indeed created. This method can be use to create an account of any type.
AccountManager.addAccountExplicitely() allows you to create an Account without user approval/interaction, but it is limited to account type for which the authenticator have the same signature as yours.
I have completed all operation related to syncAdapter but now i m stuck on one minor issue
Auth Token
After 2hr my token just expired and then i need to show user a dialog to enter his password again so that he can renew his token.
AccountManager.get(getContext()).getAuthToken(account, LoginActivity.ACCOUNT_TYPE, null, false, new AccountManagerCallback<Bundle>() {
#Override
public void run(AccountManagerFuture<Bundle> arg0) {
try {
arg0.getResult();
} catch (OperationCanceledException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (AuthenticatorException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, null);
I m running this on onPerformSync but this is not opening an activity.
There are two parts to doing this
1) in your AbstractThreadedSyncAdapter implementation of overridden onPerformSync method you need to
Get the authcode from the AccountManager using method blockingGetAuthToken()
Try to use the authcode to perform your sync processes (i.e. web service call or whatever you use it for)
If the previous step failed because authcode has expired (e.g. your web serivce returns some kind of authcode expired message) then you need to invalidate the authcode via the AccountManager using method invalidateAuthToken()
2) in your AbstractAccountAuthenticator implementation of overridden getAuthToken() method
Use the AccountManager to retrieve the password that the user last provided and try to get a new authcode from your web service using those credentials.
If the previous step failed then add an intent to open your login activity to the bundle that is returned from the getAuthToken() method. This will cause the login screen to display
Example
#Override
public Bundle getAuthToken(AccountAuthenticatorResponse oResponse, Account oAccount, String strAuthTokenType, Bundle options)
throws NetworkErrorException {
// Validate the authentication type
if (!strAuthTokenType.equals("TODO: your auth token type URL here"))
{
final Bundle result = new Bundle();
result.putString(AccountManager.KEY_ERROR_MESSAGE, "invalid authTokenType");
return result;
}
// Try to get the password already stored in account manger, if there is one
final AccountManager oAccountManager = AccountManager.get(moContext);
final String strPassword = oAccountManager.getPassword(oAccount);
if (strPassword != null)
{
// TODO: Call the authentication web service method to get a fresh authcode
// Pass the strPassword and oAccount.name
Boolean blnVerified = //TODO: were the username + password authenticated?
String strNewAuthCode = //TODO: the new authcode returned by your authentication web service
// If it worked then return the result
if (blnVerified)
{
final Bundle result = new Bundle();
result.putString(AccountManager.KEY_ACCOUNT_NAME, oAccount.name);
result.putString(AccountManager.KEY_ACCOUNT_TYPE, "TODO: your account type URI here");
result.putString(AccountManager.KEY_AUTHTOKEN, strNewAuthCode);
return result;
}
}
// Password is missing or incorrect. Start the activity to ask user to provide the missing credentials.
// Open a UI form to get the user to input their username and password again
final Intent oIntent = new Intent(moContext, frmAccount_Auth.class);
oIntent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, oResponse);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, oIntent);
return bundle;
}
Well i need to authorize Google Calendar's access for a user, first google Id works fine when i use
blockingGetAuthToken
and it gets a token, i usually log on this token.
So when i tried to use other accounts i got a null token.
I searched a lot and found out that using getAuthToken is preferred as it uses a context from the activity calling it.. then i converted the whole process to use it
private static final String AUTH_TOKEN_TYPE = "oauth2:https://www.googleapis.com/auth/calendar";
public static String authorize(AndroidtestActivity parent, Account account) {
AccountManager accountManager = AccountManager.get(parent);
Bundle options= new Bundle();
Log.d("MyAPP", "Get Authorization");
try {
AccountManagerFuture<Bundle> acc=accountManager.getAuthToken ( account, AUTH_TOKEN_TYPE, options, true, null, null);
Bundle authTokenBundle = acc.getResult();
String authToken = authTokenBundle.get(AccountManager.KEY_AUTHTOKEN).toString();
Log.d("MyAPP","Token= "+authToken);
return authToken;
} catch (Exception ex) {
Logger.getLogger(GoogleAuthorize.class.getName()).log(Level.SEVERE,
null, ex);
}
return null;
}
}
but still no accounts could get a valid token, they all get a null one
then i saw this answer https://stackoverflow.com/a/2021337/1280902 and followed using invalidateAuthToken
private static final String AUTH_TOKEN_TYPE = "oauth2:https://www.googleapis.com/auth/calendar";
public static String authorize(AndroidtestActivity parent, Account account) {
AccountManager accountManager = AccountManager.get(parent);
Bundle options= new Bundle();
Log.d("MyAPP", "Get Authorization");
try {
AccountManagerFuture<Bundle> acc=accountManager.getAuthToken ( account, AUTH_TOKEN_TYPE, options, true, null, null);
Bundle authTokenBundle = acc.getResult();
String authToken = authTokenBundle.get(AccountManager.KEY_AUTHTOKEN).toString();
accountManager.invalidateAuthToken("com.google",authToken);
acc=accountManager.getAuthToken ( account, AUTH_TOKEN_TYPE, options, true, null, null);
authTokenBundle = acc.getResult();
authToken = authTokenBundle.get(AccountManager.KEY_AUTHTOKEN).toString();
Log.d("MyAPP","Token= "+authToken);
return authToken;
} catch (Exception ex) {
Logger.getLogger(GoogleAuthorize.class.getName()).log(Level.SEVERE,
null, ex);
}
return null;
}
}
but i had the same problem on every account i use, even the one that used to work at the beginning with blockingGetAuthToken
So am i missing something?
Ok it works fine when i use
getAuthToken (Account account, String authTokenType, Bundle options, Activity activity, AccountManagerCallback<Bundle> callback, Handler handler)
The activity parameter solved the problem..
I am trying to follow the lesson on developer.android.com and I am getting stuck on the
am.getAuthToken(
myAccount_, AUTH_TOKEN_TYPE,options,this,new OnTokenAcquired(),new Handler(new OnError()));
I don't get what to put in for the myAccount_; Is it accounts that is linked to the account array? Account[] accounts = accountManager.getAccountsByType("com.google");
The token part on class OnTokenAcquired is also gennerating an error saying it isn't a var, should I just make it a global var even though it is suposse to be a constant in the AccountManager.KEY_AUTHTOKEN?
This is the other link for the Authentication lesson and I am getting an error with DIALOG_ACCOUNTS, showDialog(DIALOG_ACCOUNTS) and manager.getAuthToken(account, AUTH_TOKEN_TYPE, null, activity, new AccountManagerCallback<Bundle>() in that tutorioul. I haven't gone much further in it because of the errors I am currently getting.
I don't get why these errors are happening? I assume it is just me not putting in the right vars though.
Any suggestions?
Here is the code I have copied.
public class AccountManagerActivity extends Activity {
AccountManager accountManager = AccountManager.get(this);
Account[] accounts = accountManager.getAccountsByType("com.google");
String AUTH_TOKEN_TYPE = "Manage your tasks";
String your_api_key;
String your_client_id;
String your_client_secret;
String token;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
URL url = new URL("https://www.googleapis.com/tasks/v1/users/#me/lists?key=" + your_api_key);
URLConnection conn = (HttpURLConnection) url.openConnection();
conn.addRequestProperty("client_id", your_client_id);
conn.addRequestProperty("client_secret", your_client_secret);
conn.setRequestProperty("Authorization", "OAuth " + token);
AccountManager am = AccountManager.get(this);
Bundle options = new Bundle();
am.invalidateAuthToken(token, AUTH_TOKEN_TYPE);
am.getAuthToken(
/*Error here*/ myAccount_, // Account retrieved using getAccountsByType()
AUTH_TOKEN_TYPE, // Auth scope
options, // Authenticator-specific options
this, // Your activity
new OnTokenAcquired(), // Callback called when a token is successfully acquired
new Handler(new OnError())); // Callback called if an error occurs
}
}
And then the OnTokenAcquired class
public class OnTokenAcquired implements AccountManagerCallback<Bundle> {
public void run(AccountManagerFuture<Bundle> result) {
// TODO Auto-generated method stub
// Get the result of the operation from the AccountManagerFuture.
Bundle bundle = result.getResult();
// The token is a named value in the bundle. The name of the value
// is stored in the constant AccountManager.KEY_AUTHTOKEN.
/*Error here*/ Token = bundle.getString(AccountManager.KEY_AUTHTOKEN);
Intent launch = (Intent) result./*Error here*/get(AccountManager.KEY_INTENT);
if (launch != null) {
/*Error here*/ startActivityForResult(launch, 0);
return;
}
}
}
am.invalidateAuthToken(token, AUTH_TOKEN_TYPE);
should be
am.invalidateAuthToken(AUTH_TOKEN_TYPE, token);