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..
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 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.
I'm trying to figure out how to use Google Api for accessing/editing Google SpreadSheet.
I want to have a connection always with the same spreadsheet from many devices. I got examples using the AccountManager, but i should not use the user account. There is any good turorial?
Right now i've got the following..is that right?
AccountManager accountManager = AccountManager.get(this);
ArrayList googleAccounts = new ArrayList();
// Just for the example, I am using the first google account returned.
Account account = new Account("email#gmail.com", "com.google");
// "wise" = Google Spreadheets
AccountManagerFuture<Bundle> amf = accountManager.getAuthToken(account, "wise", null, this, null, null);
try {
Bundle authTokenBundle = amf.getResult();
String authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN);
// do something with the token
//InputStream response = sgc.getFeedAsStream(feedUrl, authToken, null, "2.1");
}
catch (Exception e) {
// TODO: handle exception
}
Required permissions:
<uses-permission android:name="android.permission.ACCOUNT_MANAGER"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<uses-permission android:name="android.permission.USE_CREDENTIALS"/>
Choose needed outh token type from the table:
http://code.google.com/intl/ja/apis/spreadsheets/faq_gdata.html#Authentication
Spreadsheets Data API wise
Code sample:
public class OuthTokenActivity extends Activity {
String tag = "DEBUG";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
AccountManager mAccountManager = AccountManager.get(this);
for (Account account : mAccountManager.getAccountsByType("com.google")) {
mAccountManager.getAuthToken(account, "wise", savedInstanceState,
this, resultCallback, null);
}
}
AccountManagerCallback<Bundle> resultCallback = new AccountManagerCallback<Bundle>() {
public void run(AccountManagerFuture<Bundle> future) {
try {
Bundle result = future.getResult();
String token = (String) result.get(AccountManager.KEY_AUTHTOKEN);
String name = (String) result.get(AccountManager.KEY_ACCOUNT_NAME);
Log.d(tag, String.format("name: %s, token: %s", name, token));
} catch (Exception e) {
e.printStackTrace();
}
}
};
}
There is an API released now, available for java script, which could be run in your app. And they show how to integrate this into an Android app in a video here.
I've got an odd behaviour when using Android's AccountManager to get an auth token for a Google account.
When the app launches, the first call to getAuthToken returns a bundle with an empty string as token. The next time, I call the very same method, it returns a valid token.
Here's my code:
public String updateToken(final boolean invalidateToken, final Context c) {
String authToken = "";
try {
final AccountManager am = AccountManager.get(c);
final Account[] accounts = am.getAccountsByType("com.google");
final Bundle bundle = am.getAuthToken(accounts[0], "android", true,
null, null).getResult();
authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN)
.toString();
if (invalidateToken) {
am.invalidateAuthToken("com.google", authToken);
authToken = updateToken(false, c);
}
} catch (final Exception e) {
//Just for debugging issues.
e.printStackTrace();
}
return authToken;
}
It looks like the empty token is returned, when this method is called in the onCreate method of my activity, although it's not always the case.
Thanks in advance.
Also I don't really know when to invalidate the token. Once a day? On every start up? Or is the empty token the indicator, that the token has to be invalidated, although it returns a valid token on the very next call.
You need to invalidate the token before requesting one.
See AuthToken from AccountManager in Android Client No Longer Working