Custom Account Type with Android AccountManager

10,617

Solution 1

Android decoupling came to bite me again. It appears that both apps needed to also have a sync_adapter.xml like:

<!-- The attributes in this XML file provide configuration information for the SyncAdapter. -->
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
          android:contentAuthority="mypackage"
          android:accountType="mypackage.account"
          android:supportsUploading="true"
          android:userVisible="true"
          android:allowParallelSyncs="false"
          android:isAlwaysSyncable="true"/>

and connect that to the sync service in the AndroidManifest.xml:

<!-- Data sync service that provides the SyncAdapter to the SyncManager framework. The SyncAdapter is used to
     maintain that the set of data on the device is a subset of the data on the server -->
<service android:exported="true" android:name=".data.sync.SyncService" tools:ignore="ExportedService">
    <intent-filter>
        <action android:name="android.content.SyncAdapter"/>
    </intent-filter>
    <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/sync_adapter"/>
</service>

For completeness, my Service is implemented as follows:

/**
 * Service that provides sync functionality to the SyncManager through the {@link SyncAdapter}.
 */
public class SyncService extends Service {

    @Override
    public void onCreate() {
        synchronized (_sync_adapter_lock) {
            if (_sync_adapter == null)
                _sync_adapter = new SyncAdapter(getApplicationContext(), false);
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return _sync_adapter.getSyncAdapterBinder();
    }

    private static final Object _sync_adapter_lock = new Object();
    private static SyncAdapter _sync_adapter = null;
}

and the SyncAdapter:

/**
 * Sync adapter for KeepandShare data.
 */
public class SyncAdapter extends AbstractThreadedSyncAdapter {

    public SyncAdapter(Context context, boolean should_auto_initialize) {
        super(context, should_auto_initialize);

        //noinspection ConstantConditions,PointlessBooleanExpression
        if (!BuildConfig.DEBUG) {
            Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                @Override
                public void uncaughtException(Thread thread, Throwable throwable) {
                    Log.e("Uncaught sync exception, suppressing UI in release build.", throwable);
                }
            });
        }
    }

    @Override
    public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider,
                              SyncResult sync_result) {
        // TODO: implement sync
    }
}

Even though I'm not actually syncing any data (the apps are not even linked to any server right now), the Android framework appears to be using the settings of the SyncAdapter to figure out which account authenticator respond to the add account request.

Solution 2

I know the question is old and closed nevertheless...

Documentation of Settings.ACTION_ADD_ACCOUNT:

The account types available to add may be restricted by adding an EXTRA_AUTHORITIES extra to the Intent with one or more syncable content provider's authorities. Only account types which can sync with that content provider will be offered to the user.

I assume that´s why you had to implement an empty sync-adapter. Refering to the docs this should not have been necessary when using Settings.EXTRA_ACCOUNT_TYPES instead (I did not try it for two projects like you did but it works like a charm in a project I am currently developing and I do not need sync-adapters at all):

intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES, new String[] { "mypackage.account" });
Share:
10,617
Dandre Allison
Author by

Dandre Allison

Medium: @dandre.allison Twitter: @imminenttweet Instagram: @imminentdomain

Updated on June 05, 2022

Comments

  • Dandre Allison
    Dandre Allison almost 2 years

    I have an account type "mypackage.account" and a content authority "mypackage". I have a Service that provides an implementation of AbstractAccountAuthenticator, the addAccount method is implemented like this:

        /**
         * 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, otherwise our activity will just pass the user's credentials on to
         * the account manager.
         */
        @Override
        public Bundle addAccount(AccountAuthenticatorResponse response, String account_type, String auth_token_type,
                                 String[] required_features, Bundle options) throws NetworkErrorException {
            final Intent intent = new Intent(_context, ConnectAccountActivity.class);
            intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
            final Bundle reply = new Bundle();
            reply.putParcelable(AccountManager.KEY_INTENT, intent);
    
            return reply;
        }
    

    I provide an authenticator.xml

    <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
                       android:accountType="mypackage.account"
                       android:icon="@drawable/ic_launcher"
                       android:smallIcon="@drawable/ic_launcher"
                       android:label="@string/app_name"
                       android:accountPreferences="@xml/account_preferences" />
    

    and I define this Service in AndroidManifest.xml like this:

    <!-- Account authentication service that provides the methods for authenticating KeepandShare accounts to the
         AccountManager framework -->
    <service android:exported="true" android:name=".authenticator.AccountAuthenticatorService" android:process=":auth" tools:ignore="ExportedService">
        <intent-filter>
            <action android:name="android.accounts.AccountAuthenticator"/>
        </intent-filter>
        <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator"/>
    </service>
    

    That's the setup, now when I want to have a screen with the list of my account type accounts on the device with an action to add a new account, I have the add account action that looks like this:

    final Intent intent = new Intent(Settings.ACTION_ADD_ACCOUNT);
    intent.putExtra(Settings.EXTRA_AUTHORITIES, new String[]{ "mypackage" });
    startActivity(intent);
    

    At this point I'm led to an account type picker that shows "mypackage.account" and "anotherpackage.account" as options. ("anotherpackage.account" is defined in another app I work on) This doesn't seem to be like the intended behavior. I've checked about 20 times that the authorities defined by both apps are different - and they are. Can someone show me what I'm missing?

  • Tim T
    Tim T about 11 years
    This is a very comprehensive and helpful answer to this question. I had added account management following the "SampleSyncAdaptor" example from the SDK and although it did good things like jump to my app when "Add Account" was touched, the added account would not show up in the phone settings. I added Dandre's code and bingo - it works. Watch out for "Ln" though - you don't need roboguice it to make this work - should probably be removed from the code.
  • Dandre Allison
    Dandre Allison about 11 years
    @TimT thanks for the comment. I actually ripped Ln out of Roboguice, I personally use Dagger instead. It looks like I only use Ln once here, so I'll edit it to use Log.
  • Udinic
    Udinic about 11 years
    Seems pretty strange that you need to add a sync adapter even though you're no using it. Try to follow the instructions on this post to create you own authenticator, and see if the problem persists: udinic.wordpress.com/2013/04/24/…
  • Dandre Allison
    Dandre Allison about 11 years
    @Udinic have you tried doing it with two separate apps, both with their own custom accounts? Settings.ACTION_ADD_ACCOUNT uses the authority, it seems, to scope accounts. And it seems that the sync_adapter.xml is the only location and way to relate an account type to an authority. I think that is what I gathered from my experience.
  • Udinic
    Udinic about 11 years
    Why do you need to create 2 account types? You can create one account type and use different auth token types to provide different access. You don't see Google creating 2 account types for Calendar, Drive and Gmail. Google has one account type and many auth token types for the different services they provide.
  • Dandre Allison
    Dandre Allison about 11 years
    @Udinic "2 separate apps", they are completely unrelated, hence they have their own account types. My point, if your app and my app don't specify the authority through SyncAdapter, and both end up on a users device. Then, the user will be seeing both when we expect one or the other be be shown. (To elaborate, one app is a personal project, the other is one I'm doing for work)
  • Udinic
    Udinic about 11 years
    Can you please explain more about this use case? If those 2 apps are completely unrelated - what are they sharing between them that needs to be in the accountmanager/syncadapter level? For most cases like this, data sharing is made using content providers and/or intents.
  • Dandre Allison
    Dandre Allison about 11 years
    @Udinic NOTHING! That's why this question exists. They are completely separate, yet, if I didn't include the SyncAdapter stuff in each one, both show up where you expect only one to show up. As I said, I suspect, that until you specify the sync_adapter.xml Android doesn't have an authority to scope the custom account type under.
  • Dandre Allison
    Dandre Allison about 11 years