Question

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;
}
Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top