Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I implement an Account on Android without a SyncAdapter

I am implementing a login system for an Android application utilizing the built-in accounts system (with the AccountManager APIs).

All is well and good on Android 2.2+, but on Android 2.1 not including a SyncAdapter causes reboots in the account settings screen (see http://code.google.com/p/android/issues/detail?id=5009 and AccountManager without a SyncAdapter?)

To get around this I implemented a stub SyncAdapter, which just returns null from IBinder onBind(Intent intent), and added the relevant stuff to the manifest. This resolves the reboot issue on Android 2.1.

However it introduces a further problem: after an account is added the Android system will, sometime later, initiate an account sync. Although no errors occur (indeed my SyncAdapter does nothing, it has no way to cause errors unless by returning null), the sync icon stays stuck in the notification bar at the top. This results in the Android sync system maintaining a permanent wake-lock, preventing the device from sleeping.

The account does not list any syncable components in the account settings screen (under the 'Data and synchronization' header), and always displays 'Sync is off' for the sync status in the list of accounts (even while the sync icon is visible in the notifications bar). Disabling account syncing does not remove the problem. Removing the account stops the problem.

My guess is I should not be returning null. Should I be returning a basic implementation of ThreadedSyncAdapter? Any help getting an account system without an associated sync working properly on 2.1 and 2.2+ is much appreciated.

like image 219
Joseph Earl Avatar asked Mar 18 '11 22:03

Joseph Earl


1 Answers

Since this is the only question I've seen related to this problem, here's a >year late answer. I also came across the permanent wake-lock problem due to the android system syncing my custom account automatically.

The best way to handle this, which requires minimum code and actually makes it so the account never syncs unless specifically called to sync in code:

ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 0);

Now this requires that the moment you create your account you call this static method. Whereas the first parameter being the account to set this setting for, the second parameter being the used contentprovider's authority, and the third being the integer that when set to a positive number enables syncing, when set to 0 disables syncing and when set to anything else makes it unknown. The authority to use can be found inside your "sync_something.xml" under the contentAuthority attribute, which is used by your SyncAdapter :

<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="com.android.contacts" 
android:accountType="com.myapp.account"/> <!-- This being your own account type-->

The above xml file is specified inside the service part of your AndroidManifest.xml:

<service android:name=".DummySyncAdapterService"
        exported="true"
        android:process=":contacts">
        <intent-filter>
            <action android:name="android.content.SyncAdapter" />
        </intent-filter>
        <meta-data android:name="android.content.SyncAdapter" 
            android:resource="@xml/sync_something" /> <!--This points to your SyncAdapter XML-->
    </service>

This is the code snippet I use to create my custom account inside my LoginActivity:

Account account = new Account("John Doe", "com.myapp.account");
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 0);
AccountManager am = AccountManager.get(LoginActivity.this);
boolean accountCreated = am.addAccountExplicitly(account, "Password", null);
Bundle extras = LoginActivity.this.getIntent().getExtras();
if(extras != null){
    if (accountCreated) { 
        AccountAuthenticatorResponse response = extras.getParcelable(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
        Bundle result = new Bundle();
        result.putString(AccountManager.KEY_ACCOUNT_NAME, "John Doe");
        result.putString(AccountManager.KEY_ACCOUNT_TYPE, "com.myapp.account");
        response.onResult(result);
    }
}

The great part of this is that when the system tries to sync the service, it checks if the service is syncable first, if it is set to false it cancels the syncing. Now you don't have to create your own ContentProvider nor does your ContentProvider get shown under Data and Synchronization. However you do need to have a stub implementation of AbstractThreadedSyncAdapter which returns an IBinder inside it's onBind method. And last but not least it makes it so that an user can't enable syncing or use the "Sync Now" button for this account unless you've added the functionality inside your app.

like image 180
Joep_H Avatar answered Sep 20 '22 14:09

Joep_H