I have an app that registers users using AccountManager and custom AccountAuthenticator.
I need to implement the Log Out
functionality.
When user clicks the Log Out
button, their account gets removed successfully, but the activity stays the same, the user doesn't get redirected to the AccountAuthenticatorActivity
, although if I close the app and open it again, it will show the authentication screen (i.e. the account actually was removed).
Do I have to perform the redirection myself (using finish(); startActivity(...);
) or the Authenticator and AccountManager should handle it for me (because I thought that if it is declared as a service, it should)?
Maybe I have to implement some sort of account removal listener?
Anyway, here's how I delete the account in MainActivity
:
private void performLogout() {
Account[] accounts = accountManager.getAccountsByType(AccountGeneral.ACCOUNT_TYPE);
if (accounts.length != 0) {
accountManager.clearPassword(accounts[0]);
accountManager.invalidateAuthToken(AccountGeneral.ACCOUNT_TYPE,
accountManager.getAuthToken(accounts[0], AccountGeneral.AUTHTOKEN_TYPE_FULL_ACCESS, null, true,
accountManagerFuture -> {
try {
Log.d("invalidateAuthToken", accountManagerFuture.getResult().toString());
} catch (android.accounts.OperationCanceledException | AuthenticatorException | IOException e) {
e.printStackTrace();
}
}, null).toString());
if (Build.VERSION.SDK_INT < 23) { // use deprecated method
accountManager.removeAccount(accounts[0], accountManagerFuture -> {
try {
if (accountManagerFuture.getResult()) {
Log.d("Deprecated ACCOUNT REMOVAL", "ACCOUNT REMOVED");
}
} catch (android.accounts.OperationCanceledException | IOException | AuthenticatorException e) {
e.printStackTrace();
}
}, null);
} else {
accountManager.removeAccount(accounts[0], this, accountManagerFuture -> {
try {
if (accountManagerFuture.getResult() != null) {
Log.d("ACCOUNT REMOVAL", "ACCOUNT REMOVED");
}
} catch (android.accounts.OperationCanceledException | AuthenticatorException | IOException e) {
e.printStackTrace();
}
}, null);
}
}
}
BTW, when I click Log Out
, I can see in the logs the following line:
D/invalidateAuthToken: Bundle[{intent=Intent { cat=[2] cmp=discounty.com/.activities.LoginActivity (has extras) }}]
so it seems that my LoginActivity
(AccountAuthenticatorActivity) actually wants to appear but something prevents it from doing so.
In my custom AccountAuthenticator
I have this method implemented (and a couple of others responsible for account and token creation):
@Override
public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response, Account account) throws NetworkErrorException {
Bundle bundle = new Bundle();
boolean allowed = true;
bundle.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, allowed);
return bundle;
}
and in my manifest I have the service declared the following way:
<service
android:name=".authenticator.DiscountyAuthenticationService"
android:process=":auth">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
</service>
So, do I have to redirect the users manually or I need to improve something in my code to make AccountManager handle the redirection for me?
The AccountManager doesn't start another account setup when you remove an account. If you want that behavior, you need to implement it yourself.
However, I would not advise to implement it that way. If you click on "log out" you don't expect to be redirected directly to the login screen again.
Instead I would show a success message like "Logged out successfully" with two buttons, one that says something like "Close" and closes the app and one that says something like "login again" which takes the user to the login screen again. That's more user friendly, IMHO.
Anyway, to answer your question: If you want to go to the login screen automatically I see the following options:
Just launch the Account setup (using AccountManager.addAccount(...)) when the user hits "logout" (optionally remove the account with AccountManager.removeAccountExplicitly(...)). Removing an account (of your own app) is unlikely to fail (especially if you allows return true
in getAccountRemovalAllowed(...)
), so in most cases it should be safe to assume the removal succeeded. Your Authenticator has to deal with the case that the account already exists anyway, since the user can launch the account setup at any time.
Wait for accountManagerFuture.getResult()
and start the account setup just like in #1 only if it returns success.
Register an OnAccountsUpdateListener using addOnAccountsUpdatedListener(...) in your Activity that checks if the account exists or not. Note that there are three triggers that cause this listener to be called:
a. An account has been created, including accounts of other apps
b. An account has been removed, including accounts of other apps
c. The password of an account has been updated, also including accounts of other apps (IMHO this event should be private to the authenticator and should definitely not broadcasted).
However, the callback won't receive any information what the trigger was. So you need to find out yourself if your account was removed.
Also note, that your code seems to call accountManagerFuture.getResult()
in the main thread, which is not a good idea since it may block. The documentation of AccountManager.removeAccount(...) explicitly says this is not allowed. Even if you're calling it in the AccountManagerCallback
callback and you probably can assume that it's safe to call it I wouldn't rely on it and make sure it's executed in a background thread. If you don't let the user confirm the removal, you probably should call AccountManager.removeAccountExplicitly(...) instead.