Home > Android > Writing an Android Sync Provider: Part 1

Writing an Android Sync Provider: Part 1

One of the highlights of the Android 2.0 SDK is that you can write custom sync providers to integrate with the system contacts, calendars, etc. The only problem is that there’s very little documentation on how it all fits together. And worse, if you mess up in certain places, the Android system will crash and reboot! Always up for a challenge, I’ve navigated through the sparse documentation, vague mailing list posts, and the Android source code itself to build a sync provider for our Last.fm app. Want to know how to build your own? Read on!

Account Authenticators

The first piece of the puzzle is called an Account Authenticator, which defines how the user’s account will appear in the “Accounts & Sync” settings. Implementing an Account Authenticator requires 3 pieces: a service that returns a subclass of AbstractAccountAuthenticator from the onBind method, an activity to prompt the user to enter their credentials, and an xml file describing how your account should look when displayed to the user. You’ll also need to add the android.permission.AUTHENTICATE_ACCOUNTS permission to your AndroidManifest.xml.

The Service

The authenticator service is expected to return a subclass of AbstractAccountAuthenticator from the onBind method — if you don’t, Android will crash and reboot when you try to add a new account to the system. The only method in AbstractAccountAuthenticator we really need to implement is addAccount, which returns an Intent that the system will use to display the login dialog to the user. The implementation below will launch our app’s main launcher activity with an action of “fm.last.android.sync.LOGIN” and an extra containing the AccountAuthenticatorResponse object we use to pass data back to the system after the user has logged in.

AccountAuthenticatorService.java

  1. import fm.last.android.LastFm;
  2. import android.accounts.AbstractAccountAuthenticator;
  3. import android.accounts.Account;
  4. import android.accounts.AccountAuthenticatorResponse;
  5. import android.accounts.AccountManager;
  6. import android.accounts.NetworkErrorException;
  7. import android.app.Service;
  8. import android.content.Context;
  9. import android.content.Intent;
  10. import android.os.Bundle;
  11. import android.os.IBinder;
  12. import android.util.Log;
  13.  
  14. /**
  15.  * Authenticator service that returns a subclass of AbstractAccountAuthenticator in onBind()
  16.  */
  17. public class AccountAuthenticatorService extends Service {
  18.  private static final String TAG = "AccountAuthenticatorService";
  19.  private static AccountAuthenticatorImpl sAccountAuthenticator = null;
  20.  
  21.  public AccountAuthenticatorService() {
  22.   super();
  23.  }
  24.  
  25.  public IBinder onBind(Intent intent) {
  26.   IBinder ret = null;
  27.   if (intent.getAction().equals(android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT))
  28.    ret = getAuthenticator().getIBinder();
  29.   return ret;
  30.  }
  31.  
  32.  private AccountAuthenticatorImpl getAuthenticator() {
  33.   if (sAccountAuthenticator == null)
  34.    sAccountAuthenticator = new AccountAuthenticatorImpl(this);
  35.   return sAccountAuthenticator;
  36.  }
  37.  
  38.  private static class AccountAuthenticatorImpl extends AbstractAccountAuthenticator {
  39.   private Context mContext;
  40.  
  41.   public AccountAuthenticatorImpl(Context context) {
  42.    super(context);
  43.    mContext = context;
  44.   }
  45.  
  46.   /*
  47.    *  The user has requested to add a new account to the system.  We return an intent that will launch our login screen if the user has not logged in yet,
  48.    *  otherwise our activity will just pass the user's credentials on to the account manager.
  49.    */
  50.   @Override
  51.   public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options)
  52.     throws NetworkErrorException {
  53.    Bundle reply = new Bundle();
  54.    
  55.    Intent i = new Intent(mContext, LastFm.class);
  56.    i.setAction("fm.last.android.sync.LOGIN");
  57.    i.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
  58.    reply.putParcelable(AccountManager.KEY_INTENT, i);
  59.    
  60.    return reply;
  61.   }
  62.  
  63.   @Override
  64.   public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) {
  65.    return null;
  66.   }
  67.  
  68.   @Override
  69.   public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
  70.    return null;
  71.   }
  72.  
  73.   @Override
  74.   public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
  75.    return null;
  76.   }
  77.  
  78.   @Override
  79.   public String getAuthTokenLabel(String authTokenType) {
  80.    return null;
  81.   }
  82.  
  83.   @Override
  84.   public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
  85.    return null;
  86.   }
  87.  
  88.   @Override
  89.   public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) {
  90.    return null;
  91.   }
  92.  }
  93. }

The account authenticator service should be defined in your AndroidManifest.xml, with a meta-data tag referencing an xml definition file, as follows:

Snippet from AndroidManifest.xml

  1. <service android:name="AccountAuthenticatorService"
  2.  android:exported="true" android:process=":auth">
  3.  <intent-filter>
  4.   <action android:name="android.accounts.AccountAuthenticator" />
  5.  </intent-filter>
  6.  <meta-data android:name="android.accounts.AccountAuthenticator"
  7.   android:resource="@xml/authenticator" />
  8. </service>

The Activity

If you don’t already have a login screen, there’s a convenience class AccountAuthenticatorActivity you can subclass that will pass your response back to the authentication manager for you, however if you already have a login activity in place you may find it easier to just pass the data back yourself, as I have done here. When the user has successfully been authenticated, we create an Account object for the user’s credentials. An account has an account name, such as the username or email address, and an account type, which you will define in your xml file next. You may find it easier to store your account type in strings.xml and use getString() to fetch it, as it is used in multiple places.

Snippet from the Last.fm login activity

  1. Account account = new Account(username, getString(R.string.ACCOUNT_TYPE)));
  2. AccountManager am = AccountManager.get(this);
  3. boolean accountCreated = am.addAccountExplicitly(account, password, null);
  4.  
  5. Bundle extras = getIntent.getExtras();
  6. if (extras != null) {
  7.  if (accountCreated) {  //Pass the new account back to the account manager
  8.   AccountAuthenticatorResponse response = extras.getParcelable(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
  9.   Bundle result = new Bundle();
  10.   result.putString(AccountManager.KEY_ACCOUNT_NAME, username);
  11.   result.putString(AccountManager.KEY_ACCOUNT_TYPE, getString(R.string.ACCOUNT_TYPE));
  12.   response.onResult(result);
  13.  }
  14.  finish();
  15. }

The XML definition file

The account xml file defines what the user will see when they’re interacting with your account. It contains a user-readable name, the system account type you’re defining, various icons, and a reference to an xml file containing PreferenceScreens the user will see when modifying your account.

authenticator.xml

  1. <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
  2.     android:accountType="fm.last.android.account"
  3.     android:icon="@drawable/icon"
  4.     android:smallIcon="@drawable/icon"
  5.     android:label="@string/app_name"
  6.     android:accountPreferences="@xml/account_preferences"/>

account_preferences.xml

  1. <PreferenceScreen
  2.   xmlns:android="http://schemas.android.com/apk/res/android">
  3.     <PreferenceCategory
  4.             android:title="General Settings" />
  5.  
  6.     <PreferenceScreen
  7.         android:key="account_settings"
  8.         android:title="Account Settings"
  9.         android:summary="Sync frequency, notifications, etc.">
  10.         <intent
  11.             android:action="fm.last.android.activity.Preferences.ACCOUNT_SETUP"
  12.             android:targetPackage="fm.last.android"
  13.             android:targetClass="fm.last.android.activity.Preferences" />
  14.     </PreferenceScreen>
  15. </PreferenceScreen>

Putting it all together

Now we’re ready for testing! The Android accounts setting screen doesn’t handle exceptions very well — if something goes wrong, your device will reboot! A better way to test is to launch the emulator, run the “Dev Tools” app, and pick “AccountsTester”.

You should see your new account type in the list, along with the built-in “Corporate” account type. Go ahead and select your account type from the drop-down list, and then press the “Add” button, and you should be presented with your login activity. After authenticating, your account should appear in a list below the buttons. At this point, it should be safe to use the system “Accounts & Sync” settings screen to remove or modify your account.

Ready to fill in that section below “Data & synchronization”? Let’s move on to part 2!

The source code for the implementation referenced here is available in my Last.fm github project under the terms of the GNU General Public License. A standalone sample project is also available here under the terms of the Apache License 2.0. Google has also released their own sample sync provider on the Android developer portal that’s a bit more complete than mine.

  1. narayan soni
    August 17th, 2012 at 03:50 | #1

    Hello.

    i used your code in my apps the account add successfully but when i open setting the force close error occur and if i run your code its work fine

  2. Ken Lim
    September 5th, 2012 at 02:08 | #2

    Hi I read you blog and I do not understand how to add you code to my login page…where do I put it? This is the code for my login page…..any help will be appreciated

    public class MainActivity extends Activity {

    String userName, passWord;
    EditText username, password;
    Button login, forgotPassword;

    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    // this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    username = (EditText) findViewById(R.id.username);
    password = (EditText) findViewById(R.id.password);

    login = (Button) findViewById(R.id.login);
    login.setOnClickListener(loginListener);
    }

    private OnClickListener loginListener = new OnClickListener() {
    public void onClick(View v) {
    // if (username.getText().toString().equals(“u”)
    // && password.getText().toString().equals(“pw”)) {
    Intent i = new Intent(MainActivity.this, EventList.class);
    startActivity(i);
    // }
    }
    };

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.activity_main, menu);
    return true;
    }
    }

  3. Javier
    September 20th, 2012 at 00:25 | #3

    I have an error when calling addAccountExplicitly.. in my activity..error says “caller uid 10075 is different than the authentication uid”..

    I am sure that my authenticator.xml account type is the same than activity`s account object.

    I am not sure of my manidest declared service.. here
    <service android:name="AccountAuthenticatorService"… is this name come from package.serviceClass? I have seen more examples where put ".authenticator.serviceClass"

    Any ideas about all this?
    Thanks in advance

  4. vineet
    December 23rd, 2012 at 21:01 | #4

    Hi,
    you are doing good work.
    For using your tutorial, i have create account and show contact sync menu in Account & Sync.
    I don’t understand, how to sync with own server. where we place code for contact sync?
    Please reply me, i am waiting for your response.

    Thanks in advance

  5. nikmin
    January 10th, 2013 at 06:57 | #5

    Hi is there a way to do contacts synchronization without using a token at all? I want to send username and password only. I see that it is less secure, but is there a way to do it and how to do it? I searched but I couldn’t finds any example without using a token.

  6. Ankit
    April 11th, 2013 at 00:16 | #6

    Hi can u tell me what is the username & password for this. If i have to create an account the how can create that account

  7. Angel
    August 20th, 2013 at 23:00 | #7

    I did exactly what you said. But I am not getting anything when I tap on Add button in Account Tester.

  8. Angel
    August 20th, 2013 at 23:14 | #8

    @Angel
    You have written this line
    i.setAction(“fm.last.android.sync.LOGIN”);
    do we need to change it to our package?

  9. Thiago
    December 3rd, 2013 at 03:43 | #9

    I need to sync contacts from all accounts. Is it possible?

  10. Rajesh
    December 17th, 2013 at 02:06 | #10

    Could you clarify following douts

    1. Which is Best Approach to development Email Synch Client( Exchange or Lotus Domino) is it token based or Push Method or is there any other method?.

    2. Which would be consume less battery?.

  11. Dheeraj Kumar saini
    May 10th, 2014 at 05:27 | #11

    I am using this code. But giving error.

    05-10 15:53:53.313: E/AndroidRuntime(16472): java.lang.RuntimeException: Unable to get provider service.CallLogSyncAdapter:
    java.lang.InstantiationException: can’t instantiate class service.CallLogSyncAdapter; no empty constructor

    After giving this error. my phone is hanged and now is not restarting. Can you help me.

  12. Shaiju MS
    July 2nd, 2014 at 08:49 | #12

    Is there any way toDisplay the app icons if the contact is associated with other application in phone address book

  13. Deepak Kishore
    October 27th, 2014 at 04:18 | #13

    How to sync my existing contacts with the app

Comment pages
1 2 1249
  1. January 23rd, 2010 at 12:58 | #1
  2. January 5th, 2011 at 14:51 | #2
  3. January 14th, 2011 at 00:14 | #3
  4. December 31st, 2011 at 18:39 | #4
  5. January 12th, 2013 at 12:52 | #5
  6. February 11th, 2014 at 04:03 | #6
Enter your name and either your email address or an OpenID URL. If you're a LiveJournal user, you can enter the address of your LiveJournal as your OpenID URL.

Or...