Search code examples
androidandroid-intentandroid-serviceaccountmanager

Custom Account Type with Android AccountManager


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?


Solution

  • 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.