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.
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>
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.
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.
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.
<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>
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;
}
}
package com.zoftino.content;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorActivity;
import android.accounts.AccountManager;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
public class ZoftinoAccountActivity extends AccountAuthenticatorActivity {
private AccountManager accountManager;
private final int REQ_REGISTER = 11;
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.account_login);
accountManager = AccountManager.get(getBaseContext());
}
public void createAccount(View view) {
Intent intent = new Intent(getBaseContext(), ZoftinoCreateAccountActivity.class);
intent.putExtras(getIntent().getExtras());
startActivityForResult(intent, REQ_REGISTER);
}
public void login(View view) {
final String userId = ((EditText) findViewById(R.id.user)).getText().toString();
final String passWd = ((EditText) findViewById(R.id.password)).getText().toString();
final String accountType = getIntent().getStringExtra(AccountManager.KEY_ACCOUNT_TYPE);
new AsyncTask<Void, Void, Intent>() {
@Override
protected Intent doInBackground(Void... params) {
Bundle data = new Bundle();
String authToken = ZoftinoAccountRegLoginHelper.authenticate(userId, passWd);
String tokenType = ZoftinoAccountRegLoginHelper.getTokenType(userId);
data.putString(AccountManager.KEY_ACCOUNT_NAME, userId);
data.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType);
data.putString(ZoftinoAccountAuthenticator.TOKEN_TYPE, tokenType);
data.putString(AccountManager.KEY_AUTHTOKEN, authToken);
data.putString(ZoftinoAccountAuthenticator.PASSWORD, passWd);
final Intent result = new Intent();
result.putExtras(data);
return result;
}
@Override
protected void onPostExecute(Intent intent) {
setLoginResult(intent);
}
}.execute();
}
private void setLoginResult(Intent intent) {
String userId = intent.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
String passWd = intent.getStringExtra(ZoftinoAccountAuthenticator.PASSWORD);
final Account account = new Account(userId, intent.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE));
if (getIntent().getBooleanExtra(ZoftinoAccountAuthenticator.ADD_ACCOUNT, false)) {
String authtoken = intent.getStringExtra(AccountManager.KEY_AUTHTOKEN);
String tokenType = intent.getStringExtra(ZoftinoAccountAuthenticator.TOKEN_TYPE);
accountManager.addAccountExplicitly(account, passWd, null);
accountManager.setAuthToken(account, tokenType, authtoken);
} else {
accountManager.setPassword(account, passWd);
}
setAccountAuthenticatorResult(intent.getExtras());
setResult(RESULT_OK, intent);
finish();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK && requestCode == REQ_REGISTER) {
setLoginResult(data);
} else
super.onActivityResult(requestCode, resultCode, data);
}
}
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();
}
}
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();
}
}
<?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>
<?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>
<?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>
<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>
<?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>
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)