Recently I had to implement an online sync facility for my habit tracker app. As it goes with every online service, you needed an account to take it’s advantage. Enter the Android Account Manager.

Now, if you are an advanced user of Android phones (and since you’re reading this, you most certainly are), you must have seen an “Accounts” section in phone Settings.

Upon clicking that, you’ll find your accounts related to apps on your phone.

I wanted to utilize this built-in AccountManager functionality to store and authenticate user’s credentials because it works like a charm with the built-in sync mechanism (using the SyncAdapter ).

But we are getting ahead of ourselves here. Let’s start with the authentication system for server.

Authentication system

Our system authenticates users based on email and password combo. If the combo is correct, server returns an auth_token which are valid until the user logs out. This is your normal token based authentication where the token lives forever and has all the permissions as the user itself.

Every device creates a new auth_token for itself because it makes it easy to log the device, limit the number of clients for an account.

This was fairly easy to implement on the server side by adding fields for auth_token in Device model. Simply create a new Device whenever user logs in or registers, populating that auth_token field to a random value.

Creating account on the phone via the Android Account Manager

Till now, we had discussed the how to create account on server, and how it interacts with the client. Let’s move on to the meat of this post, creating account using AccountManager .

Wait, what’s AccountManager? And why should we use it?

More specifically, why not store the credentials using SharedPreference and have full control without wasting time on this?

The great thing with AccountManager is that it solves the corner cases, and little small details with ease, which you might forget to account for. It really shines well when you’re using OAuth2 and need to fetch a new token when the current one expires.

There are many more benefits to using AccountManager but going through all of them is a little too much. I’ll add the links in comments if you ask for them. 🙂

A more in-depth look at AccountManager can be found here.

We’ll be using it to store the user credentials and auth_token .

Things we need to support

Users can add an account They can sync their data They can log out of their account

Pretty simple and straightforward.

Flow and important terms

Before we look at the code, let’s talk about the workflow.

When the app tries to access an account, and it’s auth_token , this is how it goes :-

The app asks the AccountManager for an auth_token .

for an . The AccountManager asks the AccountAuthenticator whether it has a token

asks the whether it has a token If it has none, it shows the user an AccountsActivity through which the user logs in

through which the user logs in The user logs in after which the app obtains the auth_token from server

from server The auth_token is stored by the AccountManager for any future use which it returns the next time app asks for it

The good thing about using AccountManager is that system will start the correct activities and handle most of the corner cases we as developers get lazy about.

This assures us that we’ll have a valid auth_token while syncing, so we can avoid defensive programming.

Building our Authenticator

Authenticator is the class which AccountManager uses to handle our account related tasks like storing the auth_token or account password.

The first thing you need to do is extend the AbstractAccountAuthenticator and implement its methods to create our Authenticator.

You may decide to implement all the methods, or just leave out some by having them throw an UnsupportedOperationException . You will, however need to implement the addAccount method to allow users to add an account in the system.

@Override public Bundle addAccount ( AccountAuthenticatorResponse response, String accountType, String authTokenType, String [ ] requiredFeatures, Bundle options ) throws NetworkErrorException { final Intent intent = new Intent ( mContext, AccountsActivity. class ) ; // This key can be anything. Try to use your domain/package intent. putExtra ( "YOUR ACCOUNT TYPE" , accountType ) ; // This key can be anything too. It's just a way of identifying the token's type (used when there are multiple permissions) intent. putExtra ( "full_access" , authTokenType ) ; // This key can be anything too. Used for your reference. Can skip it too. intent. putExtra ( "is_adding_new_account" , true ) ; // Copy this exactly from the line below. intent. putExtra ( AccountManager. KEY_ACCOUNT_AUTHENTICATOR_RESPONSE , response ) ; final Bundle bundle = new Bundle ( ) ; bundle. putParcelable ( AccountManager. KEY_INTENT , intent ) ; return bundle ; } @Override public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException { final Intent intent = new Intent(mContext, AccountsActivity.class); // This key can be anything. Try to use your domain/package intent.putExtra("YOUR ACCOUNT TYPE", accountType); // This key can be anything too. It's just a way of identifying the token's type (used when there are multiple permissions) intent.putExtra("full_access", authTokenType); // This key can be anything too. Used for your reference. Can skip it too. intent.putExtra("is_adding_new_account", true); // Copy this exactly from the line below. intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); final Bundle bundle = new Bundle(); bundle.putParcelable(AccountManager.KEY_INTENT, intent); return bundle; }

Another method you should implement is getAuthToken . This method allows your SyncAdapter , and really your whole app, to acquire the auth_token for making network calls.

This is how I’ve implemented it.

@Override public Bundle getAuthToken ( AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle bundle ) throws NetworkErrorException { AccountManager am = AccountManager. get ( mContext ) ; String authToken = am. peekAuthToken ( account, authTokenType ) ; if ( TextUtils. isEmpty ( authToken ) ) { authToken = HTTPNetwork. login ( account. name , am. getPassword ( account ) ) ; } if ( ! TextUtils. isEmpty ( 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 you reach here, person needs to login again. or sign up // 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 which is the AccountsActivity in my case. final Intent intent = new Intent ( mContext, AccountsActivity. class ) ; intent. putExtra ( AccountManager. KEY_ACCOUNT_AUTHENTICATOR_RESPONSE , response ) ; intent. putExtra ( "YOUR ACCOUNT TYPE" , account. type ) ; intent. putExtra ( "full_access" , authTokenType ) ; Bundle retBundle = new Bundle ( ) ; retBundle. putParcelable ( AccountManager. KEY_INTENT , intent ) ; return retBundle ; } @Override public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle bundle) throws NetworkErrorException { AccountManager am = AccountManager.get(mContext); String authToken = am.peekAuthToken(account, authTokenType); if (TextUtils.isEmpty(authToken)) { authToken = HTTPNetwork.login(account.name, am.getPassword(account)); } if (!TextUtils.isEmpty(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 you reach here, person needs to login again. or sign up // 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 which is the AccountsActivity in my case. final Intent intent = new Intent(mContext, AccountsActivity.class); intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); intent.putExtra("YOUR ACCOUNT TYPE", account.type); intent.putExtra("full_access", authTokenType); Bundle retBundle = new Bundle(); retBundle.putParcelable(AccountManager.KEY_INTENT, intent); return retBundle; }

This kind of completes the Authenticator, at least for our purposes. You can have a look at the entire file by visiting this gist for Authenticator.java.

Building the Authentication Activity

Now, you must have noticed that there is an AccountsActivity here. This is a special activity which is shown to the user whenever they have to login for Authenticator to get token.

Create the activity as you normally would, making network calls, validations and everything else normally. When you wish to add the credentials to Accounts, use a method like the one below.

public void createAccount ( String email, String password, String authToken ) { Account account = new Account ( email, "YOUR ACCOUNT TYPE" ) ; AccountManager am = AccountManager. get ( this ) ; am. addAccountExplicitly ( account, password, null ) ; am. setAuthToken ( account, "full_access" , authToken ) ; } public void createAccount(String email, String password, String authToken) { Account account = new Account(email, "YOUR ACCOUNT TYPE"); AccountManager am = AccountManager.get(this); am.addAccountExplicitly(account, password, null); am.setAuthToken(account, "full_access", authToken); }

This will create the account for you to use.

Building the Authentication Service

At this point, the Activity will be ready but we haven’t registered our Authenticator with the system.

To do that, we’ll have to define an AuthenticatorService and register it in the Manifest file with special filter and meta-data.

The service is pretty simple (link) and looks like:-

public class AuthenticatorService extends Service { // Instance field that stores the authenticator object // Notice, this is the same Authenticator class we defined earlier private Authenticator mAuthenticator ; @Override public void onCreate ( ) { // Create a new authenticator object mAuthenticator = new Authenticator ( this ) ; } /* * When the system binds to this Service to make the RPC call * return the authenticator's IBinder. */ @Override public IBinder onBind ( Intent intent ) { return mAuthenticator. getIBinder ( ) ; } } public class AuthenticatorService extends Service { // Instance field that stores the authenticator object // Notice, this is the same Authenticator class we defined earlier private Authenticator mAuthenticator; @Override public void onCreate() { // Create a new authenticator object mAuthenticator = new Authenticator(this); } /* * When the system binds to this Service to make the RPC call * return the authenticator's IBinder. */ @Override public IBinder onBind(Intent intent) { return mAuthenticator.getIBinder(); } }

After making this we need to declare this service in Manifest with appropriate tags.

<service android:name = "com.example.AuthenticatorService" android:enabled = "true" android:exported = "true" > <intent-filter > <action android:name = "android.accounts.AccountAuthenticator" /> </intent-filter > <meta-data android:name = "android.accounts.AccountAuthenticator" android:resource = "@xml/authenticator" /> <!-- We haven't defined this --> </service > <service android:name="com.example.AuthenticatorService" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="android.accounts.AccountAuthenticator" /> </intent-filter> <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator" /> <!-- We haven't defined this --> </service>

If you notice the meta-data tag there, you’ll find android:resource ‘s value something which hasn’t been defined before. This xml file is important because it lets the system know how to represent your app in the settings.

Create a folder named xml in the res folder. In that folder, make a new file named authenticator.xml (name can be anything, as long as you specify it in the Manifest file). The contents of that file for my project looks like this:-

<?xml version = "1.0" encoding = "utf-8" ?> <account-authenticator xmlns:android = "http://schemas.android.com/apk/res/android" android:accountType = "@string/account_type" android:icon = "@drawable/ic_launcher" android:label = "@string/app_name" android:smallIcon = "@drawable/ic_launcher" /> <?xml version="1.0" encoding="utf-8"?> <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android" android:accountType="@string/account_type" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:smallIcon="@drawable/ic_launcher" />

Most of these values would have been already defined by you, except the account_type which needs to be the same as the one your provided earlier (we have used "YOUR ACCOUNT TYPE" as placeholder).

And that’s it!

This will let you create a very basic version of app supporting the accounts in your system. Look how good our app’s name looks in Account’s screen 😛

To access your user’s account, all you will need to do is

AccountManager accountManager = AccountManager. get ( context ) ; Account [ ] accounts = accountManager. getAccountsByType ( AccountConstants. ACCOUNT_TYPE ) ; // Use accounts[0] (or whatever number of account) after checking that accounts.length > 1 AccountManager accountManager = AccountManager.get(context); Account[] accounts = accountManager.getAccountsByType(AccountConstants.ACCOUNT_TYPE); // Use accounts[0] (or whatever number of account) after checking that accounts.length > 1

That wraps up our introduction to creating an account in system. If you want a detailed information about what other things are possible, check out udinic’s post which is an excellent but overwhelming resource.

I urge you to leave a comment here if you have any questions or tweet them to me, @shobhitic.

Next week, I’ll be creating a SyncAdapter which uses this Account for communication. If you’d like to hear about that, sign up for my email list.

(The post is here – Android Sync Adapter)