How to add programmatically a custom account in android?
Asked Answered
A

3

37

I am trying to create an account for my app, where I will be able to have my contacts against my account like facebook, viber, whatsapp etc. I want my account to be visible in the account section of the settings also. Any ideas? I have googled a lot, but couldn't find a right answer where to start. Please help. What I have tried to create an account is as below. Which leads me to an error.

Account account = new Account("Title", "com.package.nom");
               String password = "password";

               AccountManager accountManager =
                       (AccountManager) MainPanel.this.getSystemService(
                               ACCOUNT_SERVICE);
               accountManager.addAccountExplicitly(account, password, null);
Aer answered 15/6, 2014 at 6:56 Comment(3)
Is there a programming question in here? It sounds like this is a how to use android question.Eastbourne
I want to do the whole thing programmatically. What I have tried is above mentioned. ThanksAer
There is also a library for account management in android here.Backgammon
Z
104

You need to setup multiple components to be able to create an account programmatically. You need:

  • an AccountAuthenticator
  • a Service to provide access to the AccountAuthenticator
  • some permissions

The authenticator

The authenticator is an object that will make the mapping between the account type and the autority (i.e. the linux-user) that have rights to manage it.

Declaring an authenticator is done in xml :

  • create a file res/xml/authenticator.xml

with the following content :

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

Note the accountType : it must be reused in code when you create the Account. The icons and label will be used by the "Settings" app to display the accounts of that type.

Implementing the AccountAuthenticator

You must extends AbstractAccountAuthenticator to do that. This will be use by third party app to access Account data.

The following sample don't allow any access to 3rd-party app and so the implementation of each method is trivial.

public class CustomAuthenticator extends AbstractAccountAuthenticator {

    public CustomAuthenticator(Context context) {
        super(context);
    }

    @Override
    public Bundle addAccount(AccountAuthenticatorResponse accountAuthenticatorResponse, String s, String s2, String[] strings, Bundle bundle) throws NetworkErrorException {
        return null;  //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public Bundle editProperties(AccountAuthenticatorResponse accountAuthenticatorResponse, String s) {
        return null;  //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public Bundle confirmCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, Bundle bundle) throws NetworkErrorException {
        return null;  //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public Bundle getAuthToken(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, String s, Bundle bundle) throws NetworkErrorException {
        return null;  //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public String getAuthTokenLabel(String s) {
        return null;  //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public Bundle updateCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, String s, Bundle bundle) throws NetworkErrorException {
        return null;  //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public Bundle hasFeatures(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, String[] strings) throws NetworkErrorException {
        return null;  //To change body of implemented methods use File | Settings | File Templates.
    }
}

The Service exposing the Account Type

Create a Service to manipulate the Accounts of that type :

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

Declare the service in your manifest :

<service android:name="com.company.demo.account.AuthenticatorService" android:exported="false">
        <intent-filter>
            <action android:name="android.accounts.AccountAuthenticator"/>
        </intent-filter>
        <meta-data
            android:name="android.accounts.AccountAuthenticator"
            android:resource="@xml/authenticator"/>
    </service>

Here, the filter and the meta-data referring to the xml resource declaring the authenticator are the key points.

The permissions

In your manifest be sure to declare the following permissions

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

(not all required for the sample code presented in this post, but you will probably have some more code about account management and at the end all of them will be useful)

Create an account in code

Now that everything is ready you create an account with the following code. Note the boolean returned by addAccountExplicitly informing you about the success or failure.

    AccountManager accountManager = AccountManager.get(this); //this is Activity
    Account account = new Account("MyAccount","com.company.demo.account.DEMOACCOUNT");
    boolean success = accountManager.addAccountExplicitly(account,"password",null);
    if(success){
        Log.d(TAG,"Account created");
    }else{
        Log.d(TAG,"Account creation failed. Look at previous logs to investigate");
    }

Final tips

Don't install your app on external storage

If your app is installed on external storage, there are good chance that Android delete your Account data when sdcard is unmounted (since the authenticator for that account will not be accessible anymore). So to avoid this loss (on every reboot !!!) you must install the App declaring the authenticator on internal storage only :

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      android:installLocation="internalOnly"
      ...

In case of trouble

Read the logs carefully, The AccountManger is outputing many logs to help you to debug your code.

Zayas answered 15/6, 2014 at 10:4 Comment(14)
Thanks ben75, it really worked for me. Marking it as the right answer. Hope, you will keep helping me in the future.Aer
But My account name is not there where it should be. I can't show my account name,instead, it is showing only my icon. what to do now?Aer
try to define the accountLabel as a resource : android:label="@string/my_custom_account"Zayas
Thanks again ben75. It worked like a magic. Can you please show me how to synchronize this account from a server? Needed that.Aer
@Zayas Thats really helped ben.I added the account details. Would you please help me to access the account details from another app. Like Messenger "Continue as XXXXX" to proceed further!Bristle
There is also a library for account management in android here.Backgammon
I haven't got any information or any good reads about custom account in Android. regarding those methods to implement. Is it necessary to declare them null? There is really no comprehensive documentation about them. :((Concomitant
@ben75, App name does not appear when I click "Add Account" section in Android setting. How to resolve this?Trillion
@Trillion my best guess is that you must implement the addAccount(...) method in your authenticatorZayas
@ben75, that is implemented. I'm able to add account programmatically but not able to do so by going from Android Settings > Accounts > Add Account. As my app does not appear there in list so that manual add account action can be done. Please note, Authenticator service is embedded in app itself.Trillion
Did you really implemented this method public Bundle addAccount(AccountAuthenticatorResponse accountAuthenticatorResponse, String s, String s2, String[] strings, Bundle bundle) throws NetworkErrorException in your CustomAuthenticator ?Zayas
and you probably also need to specify a PreferenceSreen (more details here : developer.android.com/reference/android/accounts/…Zayas
@ben75, yes both(PreferenceScreen and addAccount) is implemented. For clarity I have uploaded code on Github. You can check the same here. Thanks in advance :-)Trillion
Let us continue this discussion in chat.Trillion
B
5

I have written a library for this, which gets you free from doing the chores needed for managing android accounts, such as defining a bound service, authenticator xml, etc. Working with that is in 5 simple steps:

Step 1

Add this to dependencies for build.gradle of the app:

compile 'com.digigene.android:account-authenticator:1.3.0'

Step 2

Define your authentication account type as a string in strings.xml:

<string name="auth_account_type">DigiGene</string>

Replace ‘DigiGene’ with your own account type. This is what appears in Android Accounts in this screenshot.

Step 3

Design your registration layout for registering the users (e.g. this image):

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    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.digigene.authenticatortest.MainActivity">

    <EditText
        android:id="@+id/account_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:hint="User Name"
        />

    <EditText
        android:id="@+id/password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/account_name"
        android:gravity="center_horizontal"
        android:hint="Password"
        android:inputType="textPassword"
        />

    <Button
        android:id="@+id/register"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/password"
        android:text="register"
        android:onClick="startAuthentication"/>

</RelativeLayout>

and make a new class, say MyRegistrationActivity.java, with the following code:

import com.digigene.accountauthenticator.activity.RegistrationActivity;

public class MyRegistrationActivity extends RegistrationActivity {
    private EditText accountNameEditText, passwordEditText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.registration_layout);
        accountNameEditText = (EditText) findViewById(R.id.account_name);
        passwordEditText = (EditText) findViewById(R.id.password);
    }

    public void startAuthentication(View view) {
        register(accountNameEditText.getText().toString(), passwordEditText.getText().toString(),
                null, null);
    }
}

Step 4

Make an entry layout as in here:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    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.digigene.authenticatortest.MainActivity">

    <EditText
        android:id="@+id/account_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:hint="User Name"
        />

    <Button
        android:id="@+id/register"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/account_name"
        android:text="Sign in"
        android:onClick="signIn"/>

    <Button
        android:id="@+id/add"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/register"
        android:text="Add user"
        android:onClick="addUser"/>

</RelativeLayout>

This layout goes with the following class:

import com.digigene.accountauthenticator.AuthenticatorManager;

public class MainActivity extends Activity {
    EditText accountNameEditText;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        accountNameEditText = (EditText) findViewById(R.id.account_name);
    }

    public void signIn(View view) {
        AuthenticatorManager authenticatorManager = new AuthenticatorManager(MainActivity.this,
                getString(R.string.auth_account_type), this, MyRegistrationActivity.class,
                MyInterfaceImplementation.class);
        String authTokenType = "REGULAR_USER";
        AuthenticatorManager.authenticatorManager = authenticatorManager;
        authenticatorManager.getAccessToken(accountNameEditText.getText().toString(),
                authTokenType, null);
    }

    public void addUser(View view) {
        AuthenticatorManager authenticatorManager = new AuthenticatorManager(MainActivity.this,
                getString(R.string.auth_account_type), this, MyRegistrationActivity.class,
                MyInterfaceImplementation.class);
        String authTokenType = "REGULAR_USER";
        AuthenticatorManager.authenticatorManager = authenticatorManager;
        authenticatorManager.addAccount(authTokenType, null, null);
    }
}

Step 5

This is the last step in which the methods needed to connect to the server for registration and sign-in purposes and after that are implemented. In the following, contrary to a real case, server connections are mocked, just to demonstrate the functionality of the library. You may replace the following implementation with your own real one.

import com.digigene.accountauthenticator.AbstractInterfaceImplementation;
import com.digigene.accountauthenticator.AuthenticatorManager;
import com.digigene.accountauthenticator.result.RegisterResult;
import com.digigene.accountauthenticator.result.SignInResult;
import com.digigene.accountauthenticator.result.SignUpResult;

public class MyInterfaceImplementation extends AbstractInterfaceImplementation {
    public static int accessTokenCounter = 0;
    public static int refreshTokenCounter = 0;
    public static int demoCounter = 0;
    public static int accessTokenNo = 0;
    public static int refreshTokenNo = 0;
    public final int ACCESS_TOKEN_EXPIRATION_COUNTER = 2;
    public final int REFRESH_TOKEN_EXPIRATION_COUNTER = 5;
    public final int DEMO_COUNTER = 15;

    @Override
    public String[] userAccessTypes() {
        return new String[]{"REGULAR_USER", "SUPER_USER"};
    }

    @Override
    public void doAfterSignUpIsUnsuccessful(Context context, Account account, String
            authTokenType, SignUpResult signUpResult, Bundle options) {
        Toast.makeText(context, "Sign-up was not possible due to the following:\n" + signUpResult
                .errMessage, Toast.LENGTH_LONG).show();
        AuthenticatorManager.authenticatorManager.addAccount(authTokenType, null, options);
    }

    @Override
    public void doAfterSignInIsSuccessful(Context context, Account account, String authTokenType,
                                          String authToken, SignInResult signInResult, Bundle
                                                  options) {
        demoCounter = demoCounter + 1;
        Toast.makeText(context, "User is successfully signed in: \naccessTokenNo=" +
                accessTokenNo + "\nrefreshTokenNo=" + refreshTokenNo +
                "\ndemoCounter=" + demoCounter, Toast.LENGTH_SHORT).show();
    }

    @Override
    public SignInResult signInToServer(Context context, Account account, String authTokenType,
                                       String accessToken, Bundle options) {
        accessTokenCounter = accessTokenCounter + 1;
        SignInResult signInResult = new SignInResult();
        signInResult.isSuccessful = true;
        synchronized (this) {
            try {
                this.wait(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if ((accessTokenCounter > ACCESS_TOKEN_EXPIRATION_COUNTER || demoCounter > DEMO_COUNTER)) {
            signInResult.isSuccessful = false;
            signInResult.isAccessTokenExpired = true;
            if (demoCounter < DEMO_COUNTER) {
                signInResult.errMessage = "Access token is expired";
                return signInResult;
            }
        }
        return signInResult;
    }

    @Override
    public SignUpResult signUpToServer(Context context, Account account, String authTokenType,
                                       String refreshToken, Bundle options) {
        SignUpResult signUpResult = new SignUpResult();
        synchronized (this) {
            try {
                this.wait(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        refreshTokenCounter = refreshTokenCounter + 1;
        signUpResult.isSuccessful = true;
        signUpResult.accessToken = "ACCESS_TOKEN_NO_" + accessTokenNo;
        signUpResult.refreshToken = "REFRESH_TOKEN_NO_" + refreshTokenNo;
        if (demoCounter > DEMO_COUNTER) {
            signUpResult.isSuccessful = false;
            signUpResult.errMessage = "You have reached your limit of using the demo version. " +
                    "Please buy it for further usage";
            return signUpResult;
        }
        if (refreshTokenCounter > REFRESH_TOKEN_EXPIRATION_COUNTER) {
            refreshTokenCounter = 0;
            signUpResult.isSuccessful = false;
            signUpResult.errMessage = "User credentials have expired, please login again";
            return signUpResult;
        }
        if (accessTokenCounter > ACCESS_TOKEN_EXPIRATION_COUNTER) {
            accessTokenCounter = 0;
            accessTokenNo = accessTokenNo + 1;
            signUpResult.accessToken = "ACCESS_TOKEN_NO_" + accessTokenNo;
        }
        return signUpResult;
    }

    @Override
    public RegisterResult registerInServer(Context context, Account account, String password,
                                           String authTokenType, String[] requiredFeatures,
                                           Bundle options) {
        RegisterResult registerResult = new RegisterResult();
        registerResult.isSuccessful = false;
        synchronized (this) {
            try {
                this.wait(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if (true) {  // password is checked here and, if true, refresh token is generated for the
            // user
            refreshTokenNo = refreshTokenNo + 1;
            accessTokenNo = accessTokenNo + 1;
            registerResult.isSuccessful = true;
            registerResult.refreshToken = "REFRESH_TOKEN_NO_" + refreshTokenNo;
        }
        return registerResult;
    }

    @Override
    public boolean setDoesCallbackRunInBackgroundThread() {
        return false;
    }
}

Results

The following shows the library in action. You can find the complete tutorial here and about how AccountManager in android works in these three posts from my website:part 1, part 2, part 3.

A sample app using the library

Backgammon answered 25/9, 2016 at 10:59 Comment(0)
K
4

here a code snipped I am doing it ( sorry for german commetns ) don't forget to set the propper permissions in the manifest file.

/**
 * ueberprueft, ob es den account fuer diese app schon gibt und legt ihn
 * gegebenenfalls an.
 * 
 * @param none
 * @return void
 */
public void verifyAccount() {
    if (debug)
        Log.i(TAG, "verifyAccount() ");

    boolean bereitsAngelegt = false;
    String accountType;
    accountType = this.getPackageName();

    AccountManager accountManager = AccountManager
            .get(getApplicationContext());
    Account[] accounts = accountManager.getAccounts();
    for (int i = 0; i < accounts.length; i++) {
        if (debug)
            Log.v(TAG, accounts[i].toString());
        if ((accounts[i].type != null)
                && (accounts[i].type.contentEquals(accountType))) {
            bereitsAngelegt = true;
            if (debug)
                Log.v(TAG, "verifyAccount(): bereitsAngelegt "
                        + accounts[i].type);
        }
    }

    if (!bereitsAngelegt) {
        if (debug)
            Log.v(TAG, "verifyAccount(): !bereitsAngelegt ");

        // This is the magic that addes the account to the Android Account
        // Manager

        AccountManager accMgr = AccountManager.get(this);

        String password = "some_password";

        if (debug)
            Log.d(TAG, "verifyAccount(): ADD: accountName: "
                    + Konst.accountName + " accountType: " + accountType
                    + " password: " + password);

        final Account account = new Account(Konst.accountName, accountType);
        if (debug)
            Log.v(TAG, "verifyAccount(): nach final Account account ");
        try {
            accMgr.addAccountExplicitly(account, password, null);
        } catch (Exception e1) {
            if (debug)
                Log.v(TAG, "verifyAccount(): Exception e1 " + e1.toString());
            this.finish();
        }
        if (debug)
            Log.v(TAG,
                    "verifyAccount(): nach accMgr.addAccountExplicitly() ");
    } else {
        if (debug)
            Log.v(TAG, "verifyAccount(): bereitsAngelegt ");
    }
} // end of public void verifyAccount()

i hope this helps a little bit.

Kristopherkristos answered 15/6, 2014 at 8:38 Comment(2)
I am trying this out @hans. Which permissions should I add in Menifest? I am currently using "android.permission.AUTHENTICATE_ACCOUNTS". Let me get back to you if this helps. Thanks a lot.Aer
that's right, you need android.permission.AUTHENTICATE_ACCOUNTS. BTW: boolean bereitsAngelegt could be translated as alreadyExistingKristopherkristos

© 2022 - 2024 — McMap. All rights reserved.