Android – Use AccountManager with ASP.NET Identity

User or account management is a must-have feature in business applications. For examples, one device should be used by many employees and the employees have different roles in the project. The apps must be able to authenticate user, authorize them and let them access the resources corresponding to their roles. We can manage all these things in app databases within our own tables or we can use AccountManager providing access to a centralized registry of the user’s online accounts. With AccountManager, the user enters credentials (username and password) once per account, granting applications access to online resources with “one-click” approval. In this blog post, I would like to make a small demo how we can integrate ASP.NET Identity service for authenticating user with AccountManager. I’ll only focus on addAccount part, the other functions will be discussed in other posts if necessary.

1. Prerequisites

– If you have no idea what ASP.NET Identity is, following this link Android – Introduction to ASP.NET Identity. It gives you an overview how the service works.
– As default I use AndroidAnnotations for keeping my code clean and healthy. Don’t know how it works? Follow this link Android – Introduction to AndroidAnnotations, Maven in Intellij IDEA

2. Introduction to AccountManager

If you use Google sync through Gmail account or apps such as Viber, Dropbox…, you’ll see there’re some accounts under Settings –> Personal –> Accounts and sync. For example this is from my phone

Account manager

Under each account you have options to set up app settings or sync components which belong to that account. For example, Viber allows us to sync contacts with server.

Viber sync contacts

In this demo, I’ll show you how to add an account for our own app like that. Accounts and sync is obviously a part of Android operating system, so to interact with that service we need to follow the documentation

http://developer.android.com/reference/android/accounts/package-summary.html

or this article is also good for starting

http://udinic.wordpress.com/2013/04/24/write-your-own-android-authenticator/.

For my demo, I summarize long instructions into 3 things that we need when we want to implement an addAccount action in Accounts and sync section.
AccountManager : we need an instance of this class to invoke an event such as addAccount action or querying over all available accounts. For example, when you start your app, you realize that there’s still no account available, then you can forward user to registration or login activity. If you find out there are also registered accounts, you can let user pick one of them to use.
Service: we have to define a service for our apps to listen the actions fired by AccountManager. With this service, we just want to talk to Android OS that we will handle ourselves all account actions but how we handle these actions in detail, will be defined in derived class of AbstractAccountAuthenticator
AbstractAccountAuthenticator: an derived class of AbstractAccountAuthenticator is required. In this derived one, we’ll define how we would like to react to account actions.

3. AccountManager

3.1 Activities

Our demo contains of 3 activities: MainActivity, AuthenticatorActivity and AllAccountsActivity. The MainActivity provides 2 functions: one for creating an account and one for showing all available accounts on phone. The AuthenticatorActivity and AllAccountsActivity are responsible for executing these tasks.

MainActivity

AuthenticatorActivity

AllAccountsActivity

3.2 addAccount

As I mentioned above, for any action relevant to Account and sync, we need an instance of AccountManager. So in class MainActivity we need a variable of AccountManager and invoke appropriate function for each action.

@EActivity(R.layout.main)
public class MainActivity extends Activity {
    /**
     * Called when the activity is first created.
     */

    private AccountManager accountManager;

    @AfterViews
    void afterView() {
        accountManager = AccountManager.get(this);
    }


    @Click(R.id.buttonAllAccount)
    void allAccounts() {
        final List<Account> allAccounts = new ArrayList<Account>(Arrays.asList(accountManager.getAccountsByType(CommonConstants.ACCOUNT_TYPE)));
        final ArrayList<HDAccount> hdAccounts = new ArrayList<HDAccount>();
        for (Account account:allAccounts)
        {
            HDAccount hdAccount = new HDAccount();
            hdAccount.setUserName(account.name);
            hdAccount.setToken(getToken(account,CommonConstants.AUTH_TOKEN_TYPE_SUPER_ADMIN));
            hdAccounts.add(hdAccount);
        }
        if (allAccounts.size() == 0)
            AlertMessageBox.Show(MainActivity.this, "Info", "No account found", AlertMessageBox.AlertMessageBoxIcon.Info);
        else
            AllAccountsActivity_.intent(MainActivity.this).allAccounts(hdAccounts).start();

    }

    private String getToken(Account account, String authTokenType) {
        AccountManagerFuture<Bundle> future=    accountManager.getAuthToken(account, authTokenType, null, this, null, null);
        try {
            return future.getResult().getString(AccountManager.KEY_AUTHTOKEN);
        } catch (OperationCanceledException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (AuthenticatorException e) {
            e.printStackTrace();
        }
        return  null;
    }

    @Click(R.id.buttonNewAccount)
    void addNewAccount() {
        final AccountManagerFuture<Bundle> accountManagerFuture = accountManager.addAccount(CommonConstants.ACCOUNT_TYPE, CommonConstants.AUTH_TOKEN_TYPE_SUPER_ADMIN, null, null, this, new AccountManagerCallback<Bundle>() {
            @Override
            public void run(AccountManagerFuture<Bundle> future) {
                try {
                    Bundle bundle = future.getResult();
                    AlertMessageBox.Show(getBaseContext(), "Creating new account", "Successfully", AlertMessageBox.AlertMessageBoxIcon.Info);
                } catch (OperationCanceledException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (AuthenticatorException e) {
                    e.printStackTrace();
                }
            }
        }, null);
    }
}

For adding new account, we need to call addAccount function of AccountManager. This function requires at least 3 parameters: accountType, authTokenType and callback. In our demo, I use these definitions for them

public static final String ACCOUNT_TYPE = "com.hintdesk.adminroles";
public static final String AUTH_TOKEN_TYPE_SUPER_ADMIN = "SuperAdmin";

That means when calling addAccount with these parameters I would like to add a SuperAdmin to my user management system. The addAccount function will fire an intent to operating system saying “OK Google, I have an account intent of type com.hintdesk.adminroles. Handle it for me”. The operating system will check if there is any service ready for handling this resource and invoke that service.
Because we are trying to implement Account and sync ourselves, we need to create a service and take care of that intent.

public class HDAccountAuthenticatorService extends Service {
    @Override
    public IBinder onBind(Intent intent) {
        HDAuthenticator hdAuthenticator = new HDAuthenticator(this);
        return hdAuthenticator.getIBinder();
    }
}

public class HDAuthenticator extends AbstractAccountAuthenticator {
...
@Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
	Intent intent = AuthenticatorActivity_.intent(context).accountType(accountType).authTokenType(authTokenType).response(response).get();
	Bundle bundle = new Bundle();
	bundle.putParcelable(AccountManager.KEY_INTENT, intent);
	return bundle;
}
...
}

and in AndroidManifest.xml

<service android:name=".HDAccountAuthenticatorService">
	<intent-filter>
		<action android:name="android.accounts.AccountAuthenticator"></action>
	</intent-filter>
	<meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/accountauthenticator"/>
</service>

<?xml version="1.0" encoding="utf-8"?>

<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="com.hintdesk.adminroles"
    android:icon="@drawable/ic_hd"
    android:smallIcon="@drawable/ic_hd"
    android:label="@string/account_manager_label"
    android:accountPreferences="@xml/accountpreferences">
</account-authenticator>

In code listing above, in AndroidManifest.xml file, we define an intent filter for our service. When addAccount fires an account intent of com.hintdesk.adminroles, our service will be notified and react to addAccount action. In this case, HDAuthenticator overrides function addAccount for handling the request. We will start AuthenticatorActivity for user to register an account. The parameters accountType or authTokenType can be transferred over Extras as usual.

However in this demo, I won’t use these parameters for registering because using these parameters requires extension at web service. It’s a little complicated and shouldn’t be in scope of this blog. Anyway I also pass these parameters to registration activity to let you know that we can use these parameters during the registration.

The registration process is exactly as same as in Android – Introduction to ASP.NET Identity. There is just one more step that we need to do after a successful registration is writing token to AccountManager

@Override
protected void onPostExecute(final AuthenticationResult result) {

	if (result.isSuccessful()) {
		Account account = new Account(username,CommonConstants.ACCOUNT_TYPE );
		accountManager.addAccountExplicitly(account, password, null);
		accountManager.setAuthToken(account, CommonConstants.AUTH_TOKEN_TYPE_SUPER_ADMIN, result.getAccessToken());
		AlertMessageBox.Show(AuthenticatorActivity.this,"Successful","Register successfully", AlertMessageBox.AlertMessageBoxIcon.Info);
	}
	else
		AlertMessageBox.Show(AuthenticatorActivity.this,"Error",result.getError(), AlertMessageBox.AlertMessageBoxIcon.Error);
	super.onPostExecute(result);
}

3.3 getAccountsByType

We can also list all accounts which we’ve already added to AccountManager

@Click(R.id.buttonAllAccount)
void allAccounts() {
	final List<Account> allAccounts = new ArrayList<Account>(Arrays.asList(accountManager.getAccountsByType(CommonConstants.ACCOUNT_TYPE)));
	final ArrayList<HDAccount> hdAccounts = new ArrayList<HDAccount>();
	for (Account account:allAccounts)
	{
		HDAccount hdAccount = new HDAccount();
		hdAccount.setUserName(account.name);
		hdAccount.setToken(getToken(account,CommonConstants.AUTH_TOKEN_TYPE_SUPER_ADMIN));
		hdAccounts.add(hdAccount);
	}
	if (allAccounts.size() == 0)
		AlertMessageBox.Show(MainActivity.this, "Info", "No account found", AlertMessageBox.AlertMessageBoxIcon.Info);
	else
		AllAccountsActivity_.intent(MainActivity.this).allAccounts(hdAccounts).start();

}

Account and sync

4. Conclusion

Integrating ASP.NET Identity to AccountManager is pretty easy. The most complicated thing is how AccountManager works and how to build our own AccountAuthenticator. The complete source code of the demo can be downloaded from link below
Source code: https://bitbucket.org/hintdesk/android-use-accountmanager-with-asp-net-identity

Leave a Reply

Your email address will not be published. Required fields are marked *