1

If in SyncAdapter.onPerformSync the inherent acquisition of an auth token results in the user needing to enter their password, how can you rerun or continue the sync once they have successfully logged in?

While this seems like a fairly standard feature, I've been unable to find anything on it. The AccountManager.getAuthToken docs seem to hint at using an OnAccountsUpdatedListener, but I've tried that and it never gets called. Besides, that will get called for any change to any account if I understand the docs right, which seems a bit...indirect.

EDIT

Here are the relevant parts of my code. I've closely followed the official guide and this excellent blog post by Udinic

In Authenticator.getAuthToken

// There is neither an access nor a refresh token. User has to log in
if (!tokens.hasAccess() && !tokens.hasRefresh()) {
    Intent intent = new Intent(context, LoginActivity.class);
    intent.putExtra(LoginActivity.ARG_ACCOUNT, account);
    intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); // As per AbstractAccountAuthenticator doc
    bundle.putParcelable(AccountManager.KEY_INTENT, intent);
}
...
return bundle;

In LoginActivity.finishLogin

// Store access token if one was provided. Note that this should always be the case anyway,
// otherwise the login would have been unsuccessful
if (tokens.hasAccess()) {
    manager.setAuthToken(account, Authenticator.TOKEN_TYPE_ACCESS, tokens.access);
}
// Store refresh token if one was provided.
if (tokens.hasRefresh()) {
    manager.setAuthToken(account, Authenticator.TOKEN_TYPE_REFRESH, tokens.refresh);
}
final Intent intent = new Intent();
if (tokens.hasAccess()) {
    intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name);
    intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, account.type);
    intent.putExtra(AccountManager.KEY_AUTHTOKEN, tokens.access);
}

setAccountAuthenticatorResult(intent.getExtras());
setResult(Activity.RESULT_OK, intent);
finish();

And, even though it's somewhat trivial: In SyncAdapter.onPerformSync:

String token = null;
try {
    token = AccountManager.get(getContext()).blockingGetAuthToken(account, Authenticator.TOKEN_TYPE_ACCESS, true);
    if (token == null) {
        syncResult.stats.numAuthExceptions++;
        return;
    }
} catch (OperationCanceledException x) {
    return;
} catch (AuthenticatorException x) {
    syncResult.stats.numAuthExceptions++;
    return;
} catch (IOException x) {
    syncResult.stats.numIoExceptions++;
    return;
}
Community
  • 1
  • 1
BadIdeaException
  • 2,125
  • 15
  • 32
  • You shouldn't have to re-run it. If you use AccountManager.blockingGetAuthToken, you should either get a valid token or not. – G. Blake Meike May 10 '14 at 15:56
  • No, it returns `null`. – BadIdeaException May 10 '14 at 16:03
  • Ah! Your AccountManager is not doing the right thing. The bundle it returns needs to have a value for KEY_INTENT, naming the validation Activity. That Activity needs to get the response object (also in the bundle) and call its onResponse method. – G. Blake Meike May 10 '14 at 16:18
  • Good guess, but I'm already doing that. Everything is working, except the sync being rerun. Returning `null` is part of the documented behavior of `blockingGetAuthToken`. – BadIdeaException May 10 '14 at 16:27
  • Really, you shouldn't have to re-run the sync. When blockingGetAuthToken returns you should have the validated account. If your authenticator is working correctly, it will return null *only* if the account is invalid. See: http://stackoverflow.com/questions/11376316/android-getauthtoken-returns-a-null-token – G. Blake Meike May 10 '14 at 16:39
  • Ok I'll double check that tomorrow. But why is it in the docs then? – BadIdeaException May 10 '14 at 16:49
  • I read: "returns: An auth token of the specified type for this account, or null if authentication fails or none can be fetched." – G. Blake Meike May 10 '14 at 17:10
  • Hmm maybe I misunderstand what they mean by "none can be fetched" then. I'll report back tomorrow, but thanks for your time already. :) – BadIdeaException May 10 '14 at 17:27
  • btw Shameless plug: Chapter 5 of my book "Enterprise Android" discusses this in detail. I'll be speaking on the topic at AnDevCon in Boston, later this month. – G. Blake Meike May 10 '14 at 17:58
  • @G.BlakeMeike I've double and triple checked my source and I can't see it. I've posted the relevant portions of my code, could you be so kind and take a look? Unfortunately the answer you linked above doesn't help me, because it uses the "foreground" version of `getAuthToken` that will show an activity immediately when tokens aren't available. I can't do that in sync. – BadIdeaException May 11 '14 at 09:20
  • This is interesting. Let's [continue this discussion in chat](http://chat.stackoverflow.com/rooms/52469/discussion-between-g-blake-meike-and-chris) – G. Blake Meike May 11 '14 at 15:52

1 Answers1

1

After some discussion, I believe that what you need is:

ContentResolver.requestSync(account, Contract.AUTHORITY, new Bundle());

Your Account manager is, quite reasonably, causing blockingGetAuthToken to return null, immediately, when user interaction is required to complete the sync. You just need to arrange that, when the user completes the action (validating themselves) you request a new sync. The code above will do it.

G. Blake Meike
  • 6,615
  • 3
  • 24
  • 40