ZOFTINO.COM android and web dev tutorials

Android Account Manager & Create Custom Account Type

To install apps from play store on a new device, you are required to login to Google account. To add an account, you need to go to settings and accounts. On clicking add accounts button, you are presented with account type screen listing available account types. On clicking desired account type takes you to login screen and account setup flow for that account.

By default google account type is available so that after login you can start installing apps from play store. If you install Dropbox , WhatsApp or other apps which support accounts, you will see options in account type screen for adding Dropbox and WhatApp account types.

Android system provides account manager to manage user’s online accounts. The main purpose of account manager is to eliminate the need for the user to login to the same account multiple times when user tries to use apps and online resources.

If your app or online service allows users to setup account and you want to make those user accounts usable on android device, then you need to provide components which work with android system account manager. If your app provides required components for account manager, then your account type will appear in the account type screen allowing users to login once and utilize or access your online services without needing to login again and again depending on how your account type is implemented.

Custom Account Type Screens and Flow

Accounts Screen

android setting accounts

Add Accounts Screen

android add custom account type

Custom Account Type Login Screen

android custom account type login screen

Custom Account Type Signed In

android custom account type added

Custom Account Type Preference

android added custom account type preference

How to Make Your Account Type Appear in Setting / Accounts Screen

The first point that needs to be noticed is that setting / accounts service and account manager are provided and maintained by android system. To provide your own custom account type, you need to provide certain components and configurations which the system identifies and plugs into account manager to provide your custom account type.

Service component with android.accounts.AccountAuthenticator intent filer and authenticator configuration xml is the component that the system uses to plug the defined custom account type to account manager. Service is defined in android manifest.xml as shown below.

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

Android custom account type information like account label, account type, icon and other information is defined in xml file which is defined as meta-data of service. This information is displayed on accounts screen, preference screen and is used by account manager to perform account operations.

 <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="com.zoftino.suth"
    android:icon="@drawable/zoftino"
    android:smallIcon="@drawable/zoftino"
    android:label="@string/label"
    android:accountPreferences="@xml/auth_pref"></account-authenticator>

Authenticator Service

Here the service is bound service. Method onBind() using account authenticator returns IBinder that allows system account manager to communicate with account authenticator to perform add account, get auth token and other functions to maintain your custom account type.

Account manager binds to this service so the service runs indefinitely to handle account-authenticator requests from account manager.

Account Authenticator

You need to create account authenticator by extending AbstractAccountAuthenticator and implement required methods for your custom account type. These methods are called by account manager when user tries to add account or when applications use account manager to get auth token to utilize online service associated with your custom account type.

You need to implement two main methods addAccount and getAuthToken in your account authenticator. Method addAccount is called when user tries to add account from settings. Method getAuthToken is called when apps need auth token to access online services associated with the account type.

When user tries to add account from settings, account manager calls addAccount method of your account authenticator, simple implementation of addAccount method starts an activity by firing intent with extra data.

Account Authenticator Activity

The activity, which handles the intent fired by addAccount method, presents login screen or registration screen for user to enter input, captures user id and password, authenticates login input, gets auth-token from server, adds account details to account manager and communicates result back to account manager using AccountAuthenticatorResponse. This response object is sent to activity as extra data in intent which is fired by addAccount method of account authenticator.

To store account details in account manager, call addAccountExplicitly method if new account is being added otherwise call setPassword method for existing account to store password from the activity after authentication is performed.

Required permissions

    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"></uses-permission>
    <uses-permission android:name="android.permission.GET_ACCOUNTS"></uses-permission>
    <uses-permission android:name="android.permission.INTERNET"></uses-permission>
    <uses-permission android:name="android.permission.USE_CREDENTIALS"></uses-permission>

Custom Account Type Example

AccountAuthenticator

package com.zoftino.content;

import android.accounts.AbstractAccountAuthenticator;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.accounts.NetworkErrorException;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;

import static android.accounts.AccountManager.KEY_BOOLEAN_RESULT;

public class ZoftinoAccountAuthenticator extends AbstractAccountAuthenticator {

    public static final String PASSWORD = "password";
    public static final String ADD_ACCOUNT = "addAccount";
    public static final String TOKEN_TYPE = "tokenType";

    private Context context;
    public ZoftinoAccountAuthenticator(Context ctx){
        super(ctx);
        context = ctx;
    }
    @Override
    public Bundle editProperties(AccountAuthenticatorResponse accountAuthenticatorResponse, String s) {
        return null;
    }

    @Override
    public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {

        Intent intent = new Intent(context, ZoftinoAccountActivity.class);
        intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, accountType);
        intent.putExtra(ADD_ACCOUNT, true);
        intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);

        final Bundle bundle = new Bundle();
        bundle.putParcelable(AccountManager.KEY_INTENT, intent);

        return bundle;
    }

    @Override
    public Bundle confirmCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, Bundle bundle) throws NetworkErrorException {
        return null;
    }

    @Override
    public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
        final AccountManager accountManager = AccountManager.get(context);

        String authToken = accountManager.peekAuthToken(account, authTokenType);

        if (TextUtils.isEmpty(authToken)) {
            final String password = accountManager.getPassword(account);
            if (password != null) {
                authToken = ZoftinoAccountRegLoginHelper.authenticate(account.name, password);
            }
        }

        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;
        }

        final Intent intent = new Intent(context, ZoftinoAccountActivity.class);
        intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
        intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, account.type);
        intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name);
        intent.putExtra(TOKEN_TYPE, authTokenType);
        final Bundle bundle = new Bundle();
        bundle.putParcelable(AccountManager.KEY_INTENT, intent);
        return bundle;
    }

    @Override
    public String getAuthTokenLabel(String s) {
        return "full";
    }

    @Override
    public Bundle updateCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, String s, Bundle bundle) throws NetworkErrorException {
        return null;
    }

    @Override
    public Bundle hasFeatures(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, String[] strings) throws NetworkErrorException {
        final Bundle result = new Bundle();
        result.putBoolean(KEY_BOOLEAN_RESULT, false);
        return result;
    }
}

AccountAuthenticatorActivity

package com.zoftino.content;


import android.accounts.AccountManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

public class ZoftinoCreateAccountActivity extends AppCompatActivity {

    private String accountType;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_create_account);

        accountType = getIntent().getStringExtra(AccountManager.KEY_ACCOUNT_TYPE);

    }
    public void createAccount(View view){
        final String userId = ((EditText) findViewById(R.id.user)).getText().toString();
        final String passWd = ((EditText) findViewById(R.id.password)).getText().toString();
        final String name = ((EditText) findViewById(R.id.name)).getText().toString();

        if(!ZoftinoAccountRegLoginHelper.validateAccountInfo(name,userId, passWd)){
            ((TextView)findViewById(R.id.error)).setText("Please Enter Valid Information");
        }

        String authToken = ZoftinoAccountRegLoginHelper.createAccount(name,userId, passWd);
        String authTokenType = ZoftinoAccountRegLoginHelper.getTokenType(userId);

        if(authToken.isEmpty()){
           ((TextView)findViewById(R.id.error)).setText("Account couldn't be registered, please try again.");
        }

        Bundle data = new Bundle();
        data.putString(AccountManager.KEY_ACCOUNT_NAME, userId);
        data.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType);
        data.putString(AccountManager.KEY_AUTHTOKEN, authToken);
        data.putString(ZoftinoAccountAuthenticator.PASSWORD, passWd);
        data.putString(ZoftinoAccountAuthenticator.TOKEN_TYPE, authTokenType);

        final Intent result = new Intent();
        result.putExtras(data);

        setResult(RESULT_OK, result);
        finish();
    }
}

Register Account Activity

package com.zoftino.content;


import android.accounts.AccountManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

public class ZoftinoCreateAccountActivity extends AppCompatActivity {

    private String accountType;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_create_account);

        accountType = getIntent().getStringExtra(AccountManager.KEY_ACCOUNT_TYPE);

    }
    public void createAccount(View view){
        final String userId = ((EditText) findViewById(R.id.user)).getText().toString();
        final String passWd = ((EditText) findViewById(R.id.password)).getText().toString();
        final String name = ((EditText) findViewById(R.id.name)).getText().toString();

        if(!ZoftinoAccountRegLoginHelper.validateAccountInfo(name,userId, passWd)){
            ((TextView)findViewById(R.id.error)).setText("Please Enter Valid Information");
        }

        String authToken = ZoftinoAccountRegLoginHelper.createAccount(name,userId, passWd);
        String authTokenType = ZoftinoAccountRegLoginHelper.getTokenType(userId);

        if(authToken.isEmpty()){
           ((TextView)findViewById(R.id.error)).setText("Account couldn't be registered, please try again.");
        }

        Bundle data = new Bundle();
        data.putString(AccountManager.KEY_ACCOUNT_NAME, userId);
        data.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType);
        data.putString(AccountManager.KEY_AUTHTOKEN, authToken);
        data.putString(ZoftinoAccountAuthenticator.PASSWORD, passWd);
        data.putString(ZoftinoAccountAuthenticator.TOKEN_TYPE, authTokenType);

        final Intent result = new Intent();
        result.putExtras(data);

        setResult(RESULT_OK, result);
        finish();
    }
}

Service

package com.zoftino.content;


import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

public class ZoftinoAccountTypeService extends Service {
    @Override
    public IBinder onBind(Intent intent) {
        ZoftinoAccountAuthenticator authenticator = new ZoftinoAccountAuthenticator(this);
        return authenticator.getIBinder();
    }
}

Create Account Activity Layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_register"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.zoftino.content.ZoftinoCreateAccountActivity">
    <TextView android:id="@+id/error"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="25dp"
        android:textColor="@android:color/holo_red_dark">
    </TextView>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <TextView android:id="@+id/name_l"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:padding="10dp"
            android:textSize="15sp"
            android:text="Name">
        </TextView>
        <EditText
            android:id="@+id/name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true" >
        </EditText>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <TextView android:id="@+id/user_l"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:padding="10dp"
            android:textSize="15sp"
            android:text="User Id">
        </TextView>
        <EditText
            android:id="@+id/user"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true" >
        </EditText>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <TextView android:id="@+id/password_l"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:padding="10dp"
            android:textSize="15sp"
            android:text="Password">
        </TextView>
        <EditText
            android:id="@+id/password"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:inputType="textPassword">
        </EditText>
    </LinearLayout>

    <Button
        android:id="@+id/registration_b"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="createAccount"
        android:text="Create Account"></Button>

</LinearLayout>

Authenticator Activity Layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_login"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.zoftino.content.ZoftinoAccountActivity">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <TextView android:id="@+id/user_l"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:padding="10dp"
            android:textSize="15sp"
            android:text="User Id">
        </TextView>
        <EditText
            android:id="@+id/user"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true" >
        </EditText>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <TextView android:id="@+id/password_l"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:padding="10dp"
            android:textSize="15sp"
            android:text="Password">
        </TextView>
        <EditText
            android:id="@+id/password"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:inputType="textPassword">
        </EditText>
    </LinearLayout>

    <Button
        android:id="@+id/login"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:onClick="login"
        android:text="LOGIN"></Button>
    <Button
        android:id="@+id/create_acc"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:onClick="createAccount"
        android:text="Create Account"></Button>

</LinearLayout>

Manifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.zoftino.content">
    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"></uses-permission>
    <uses-permission android:name="android.permission.GET_ACCOUNTS"></uses-permission>
    <uses-permission android:name="android.permission.INTERNET"></uses-permission>
    <uses-permission android:name="android.permission.USE_CREDENTIALS"></uses-permission> 
<application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
    <service android:name=".ZoftinoAccountTypeService">
        <intent-filter>
            <action android:name="android.accounts.AccountAuthenticator"></action>
        </intent-filter>
        <meta-data android:name="android.accounts.AccountAuthenticator"
            android:resource="@xml/zoftino_authenticator"></meta-data>
    </service>
    <activity android:name=".ZoftinoAccountActivity"></activity>
    <activity android:name=".ZoftinoCreateAccountActivity"></activity>
</application>
</manifest>

Authenticator Xml

<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="com.zoftino.suth"
    android:icon="@drawable/zoftino"
    android:smallIcon="@drawable/zoftino"
    android:label="@string/label"
    android:accountPreferences="@xml/auth_pref"></account-authenticator>

Preference Xml

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <PreferenceCategory android:title="Coupon Preferences"></PreferenceCategory>
    <CheckBoxPreference android:title="Cashback Offers"
        android:key="viewCashbackOffers"
        android:summary="Show Latest Cashback Offers"></CheckBoxPreference>
    <CheckBoxPreference android:title="Coupon Offers"
        android:key="viewCoupons"
        android:summary="Show Latest Coupons"></CheckBoxPreference>
    <CheckBoxPreference android:title="Deals Offers"
        android:key="viewDeals"
        android:summary="Show Latest Deals"></CheckBoxPreference>
    <SwitchPreference  android:key="offerEmail"
        android:title="Send Offer Email"
        android:defaultValue="true"></SwitchPreference>
</PreferenceScreen>

Custom Account Type Issue Faced

I faced below class not found issue while I was testing authenticator or custom account type with preferences on Android 7.0 API 24. The issue seemed to be specific to Android 7.0 and related to preference class not found issue reported in android issue tracker which got fixed in future release as per the information in it. I tested my code related to custom account type on Android 7.1.1 API 25 and it worked fine.

 Caused by: java.lang.ClassNotFoundException: Didn't find class "android.support.v7.preference.PreferenceScreen" on path: DexPathList[[],nativeLibraryDirectories=[/system/priv-app/TeleService/lib/x86, /system/lib, /vendor/lib, /system/lib, /vendor/lib]]
                                                                        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
                                                                        at java.lang.ClassLoader.loadClass(ClassLoader.java:380)
                                                                        at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
                                                                        at android.support.v7.preference.PreferenceInflater.createItem(PreferenceInflater.java:228)