Search code examples
javaandroidandroid-manifestandroid-contentproviderandroid-syncadapter

ContentResolver.requestSync doesn't call SyncAdapter


So I followed the tutorial on creating a SyncAdapter without a real ContentProvider and an Account (from here) but came across a problem I just can't fix.

When calling ContentResolver.requestSync() to manually force a sync, it seems, that the SyncAdapter I implemented just doesn't get called. Please note, that I created all the stub classes (StubAuthenticator, StubProvider, AuthenticatorService) and the SyncService exactly how they got created in the mentioned tutorial. If you need me to post the code either way, I will edit this post.

Android Manifest.xml

<application


    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
    <uses-permission android:name="android.permission.READ_SYNC_STATS"/>
    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
    <uses-permission android:name="android.permission.GET_ACCOUNTS"/>

    ...        

    <service
        android:name=".datasync.SyncService"
        android:exported="true"
        android:process=":sync">
        <intent-filter>
            <action android:name="android.content.SyncAdapter"/>
        </intent-filter>
        <meta-data
            android:name="android.content.SyncAdapter"
            android:resource="@xml/sync_adapter"/>
    </service>

    <service
        android:name=".datasync.AuthenticatorService">
        <intent-filter>
            <action android:name="android.accounts.AccountAuthenticator"/>
        </intent-filter>
        <meta-data
            android:name="android.accounts.AccountAuthenticator"
            android:resource="@xml/authenticator"/>
    </service>

    <provider
        android:name=".datasync.StubProvider"
        android:authorities="myexample.com.provider"
        android:enabled="true"
        android:exported="false"
        android:label="DataSyncContentProvider"
        android:syncable="true"/>

</application>

sync_adapter.xml

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
          android:contentAuthority="myexample.com.provider"
          android:accountType="myexample.com.account"
          android:userVisible="false"
          android:allowParallelSyncs="false"
          android:isAlwaysSyncable="true"/>

authenticator.xml

<?xml version="1.0" encoding="utf-8"?>
<account-authenticator
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="myexample.com.account"
    android:label="MyExampleApp"/>

DataSyncAdapter.java

public class DataSyncAdapter extends AbstractThreadedSyncAdapter{

    public DataSyncAdapter(Context context, boolean autoInitialize) {
        super(context, autoInitialize);
    }

    @Override
    public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider,
                          SyncResult syncResult) {
        Log.d(MainActivity.TAG, "onPerformSync: Called");

        //do sync stuff here
    }
}

MainActivity.java

public class MainActivity extends AppCompatActivity{
    /**
     * Tag for own application.
     */
    public static final String TAG = "com.myexample";

    @Override
    protected final void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //create UI here...

        Bundle syncExtras = new Bundle();
        //add additional sync stuff (internal)
        DataSyncUtil.createSyncRequest(this, syncExtras);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[]
        grantResults) {
        switch (requestCode) {
            case PERMISSIONS_GET_ACCOUNTS:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Bundle syncExtras = new Bundle();

                    //add additional sync stuff (internal)

                    DataSyncUtil.createSyncRequest(this, syncExtras);
                } else {
                    //TODO handle permission denied
                }
                return;
        }
    }
}

DataSyncUtil.java

public class DataSyncUtil {

    public static void createSyncRequest(Activity activity, Bundle extras) {
        Log.d(MainActivity.TAG, "createSyncRequest: Called");
        String authority = "myexample.com.provider";
        Account dummy = getDummySyncAccount(activity);
        if (dummy != null) {
            if (ContentResolver.isSyncPending(dummy, authority) ||
                ContentResolver.isSyncActive(dummy, authority)) {
                Log.i(MainActivity.TAG, "SyncPending, canceling");
                ContentResolver.cancelSync(dummy, authority);
            }
            extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
            extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);

            ContentResolver.requestSync(dummy, authority, extras);
        }
    }

    public static Account getDummySyncAccount(Activity activity) {
        String auth_type = "myexample.com.account;
        Account dummy = null;
        AccountManager accountManager = (AccountManager) activity.getSystemService(ACCOUNT_SERVICE);

        if (ContextCompat.checkSelfPermission(activity, Manifest.permission.GET_ACCOUNTS) !=
            PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.GET_ACCOUNTS},
                    MainActivity.PERMISSIONS_GET_ACCOUNTS);
        } else {
            Account[] existing = accountManager.getAccountsByType(auth_type);
            if (existing != null && existing.length > 0) {
                //TODO handle more than one account
                Log.d(MainActivity.TAG, "getDummySyncAccount: Account already exists and is getting returned");
                dummy = existing[0];
            } else {
                Log.d(MainActivity.TAG, "getDummySyncAccount: Account has to be created");
                // Create the account type and default account
                dummy = new Account(ACCOUNT, auth_type);
                // Get an instance of the Android account manager
                /*
                 * Add the account and account type, no password or user data
                 * If successful, return the Account object, otherwise report an error.
                 */
                if (accountManager.addAccountExplicitly(dummy, null, null)) {
                    ContentResolver.setIsSyncable(dummy, "myexample.com.content", 1);
                    ContentResolver.setSyncAutomatically(dummy, "myexample.com.content", true);
                } else {
                /*
                 * The account exists or some other error occurred.Log this, report it,
                 * or handle it internally.
                 */

                }
            }
        }
        Log.d(MainActivity.TAG, "getDummySyncAccount: "+dummy.name);
        return dummy;
    }

}

The code executes just fine up until ContentResolver.requestSync() in the DataSyncUtil class, but the DataSyncAdapter never gets called.

I'd appreciate any help on this :)

FYI: This is my first big scale Android project, so I'm rather inexperienced.


Solution

  • It appears I made a rookie mistake. onPerformSync() does indeed get called. But I had the filter "Show only selected application" in my android monitor set, so that the Log.d in the background sync service didn't show. This answer helped me out.