title: SyncAdapter always in pending state
body: <p>I'm currently working on an android app which relies on a <code>SyncAdapter</code> to refresh its content from a server. I basically followed these instructions: <a href="https://developer.android.com/training/sync-adapters/creating-sync-adapter.html" rel="nofollow">https://developer.android.com/training/sync-adapters/creating-sync-adapter.html</a></p>
  
  <p>This worked perfectly until recently. I know this might sound stupid, but I honestly have no idea how I screwed it up :(</p>
  
  <hr>
  
  <h2>The setup</h2>
  
  <p>I have one <code>ContentProvider</code> and therefore one <code>SyncAdapter</code> and one Account for all items i want to sync. I use integer flags to determine which item has to be synced:</p>
  
  <pre><code>public static final int EVENTS = 0x1;
  public static final int NEWS = 0x2;
  public static final int SUBSTITUTIONS = 0x4;
  public static final int TEACHERS = 0x8;
  public static final int ALL = 0xF;
  </code></pre>
  
  <p>So in my <code>onPerformSync</code> I have something like:</p>
  
  <pre><code>ArrayList&lt;ContentProviderOperation&gt; batchList = new ArrayList&lt;&gt;();
  
  int which = extras.getInt(SYNC.ARG, SYNC.ALL);
  if((which &amp; SYNC.NEWS) == SYNC.NEWS) { syncNews(provider, batchList, syncResult, 0, 1); }
  if((which &amp; SYNC.EVENTS) == SYNC.EVENTS) { syncEvents(provider, batchList, syncResult); }
  if((which &amp; SYNC.TEACHERS) == SYNC.TEACHERS) { syncTeachers(provider, batchList, syncResult); }
  if((which &amp; SYNC.SUBSTITUTIONS) == SYNC.SUBSTITUTIONS) { syncSubstitutions(provider, batchList, syncResult); }
  
  Log.i(TAG, "Merge solution ready. Applying batch update to database...");
  provider.applyBatch(batchList);
  </code></pre>
  
  <p>Because I also want the user to be able to force a refresh, I use a <code>SwipeRefreshLayout</code> to fire up the sync service:</p>
  
  <pre><code>@Override
  public void onRefresh() {
      Log.d(TAG, "Force refresh triggered!");
      SyncUtils.triggerRefresh(SyncAdapter.SYNC.NEWS | SyncAdapter.SYNC.EVENTS);
  }
  </code></pre>
  
  <p>I also want to monitor the sync state, so I register / unregister a <code>SyncStatusObserver</code> in my fragment's <code>onResume</code> / <code>onPause</code>:</p>
  
  <pre><code>private final SyncStatusObserver syncStatusObserver = new SyncStatusObserver() {
      @Override
      public void onStatusChanged(int which) {
          Account account = AuthenticatorService.getAccount(SyncUtils.ACCOUNT_TYPE);
          boolean syncActive = ContentResolver.isSyncActive(account, DataProvider.AUTHORITY);
          boolean syncPending = ContentResolver.isSyncPending(account, DataProvider.AUTHORITY);
  
          final boolean refresh = syncActive || syncPending;
          Log.d(TAG, "Status change detected. Active: %b, pending: %b, refreshing: %b", syncActive, syncPending, refresh);
  
          swipeRefreshLayout.post(new Runnable() {
              @Override
              public void run() {
                 swipeRefreshLayout.setRefreshing(refresh);
              }
          });
      }
  };
  </code></pre>
  
  <hr>
  
  <h2>The problem</h2>
  
  <p>Whenever I start the application, the <code>Refresh layout</code> is indication a sync is active. I logged nearly everything and found out that a sync is in pending state. Whenever I try to force refresh the sync will either</p>
  
  <ul>
  <li>Become active, do all the stuff and then go back to pending or</li>
  <li>Simply not become active and staying in pending mode forever</li>
  </ul>
  
  <p>Here's an example log:</p>
  
  <pre><code>D/HomeFragment﹕ Status change detected. Active: false, pending: true, refreshing: true
  D/HomeFragment﹕ Status change detected. Active: false, pending: true, refreshing: true
  D/HomeFragment﹕ Status change detected. Active: true, pending: true, refreshing: true
  D/HomeFragment﹕ Status change detected. Active: false, pending: true, refreshing: true
  </code></pre>
  
  <p>As you can see, it will never be <code>Active: false, pending: false</code> to indicate the sync finished. This really grinds my gears.</p>
  
  <hr>
  
  <h2>Some more code</h2>
  
  <p>I do the initial setup of the stub account (and the periodic syncs) in my application class:</p>
  
  <pre><code>public static void createSyncAccount(Context context) {
      boolean newAccount = false;
      boolean setupComplete = PreferenceManager
                 .getDefaultSharedPreferences(context).getBoolean(PREF_SETUP_COMPLETE, false);
  
      // Create account, if it's missing. (Either first run, or user has deleted account.)
      Account account = AuthenticatorService.getAccount(ACCOUNT_TYPE);
      AccountManager accountManager =
                  (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
  
      if (accountManager.addAccountExplicitly(account, null, null)) {    
          // Inform the system that this account supports sync
          ContentResolver.setIsSyncable(account, DataProvider.AUTHORITY, 1);
  
          // Inform the system that this account is eligible for auto sync when the network is up
          ContentResolver.setSyncAutomatically(account, DataProvider.AUTHORITY, true);
  
          // Recommend a schedule for automatic synchronization. The system may modify this based
          // on other scheduled syncs and network utilization.
          requestPeriodic(account, SYNC.EVENTS, 172800);
          requestPeriodic(account, SYNC.NEWS, 604800);
          requestPeriodic(account, SYNC.SUBSTITUTIONS, 1800);
          requestPeriodic(account, SYNC.TEACHERS, 2419200);
  
          newAccount = true;
      }
  
      // Schedule an initial sync if we detect problems with either our account or our local
      // data has been deleted. (Note that it's possible to clear app data WITHOUT affecting
      // the account list, so wee need to check both.)
      if (newAccount || !setupComplete) {
          triggerRefresh(SYNC.ALL);
          PreferenceManager.getDefaultSharedPreferences(context).edit()
                      .putBoolean(PREF_SETUP_COMPLETE, true).commit();
      }
  }
  </code></pre>
  
  <p>Where <code>requestPeriodic()</code> is the following:</p>
  
  <pre><code>public static void requestPeriodic(Account account, int which, long seconds) {
      Bundle options = new Bundle();
      options.putInt(SYNC.ARG, which);
  
      ContentResolver.addPeriodicSync(account,
          DataProvider.AUTHORITY, options, seconds);
  }
  </code></pre>
  
  <p>And my <code>triggerRefresh()</code> looks like:</p>
  
  <pre><code>public static void triggerRefresh(int which) {
      Log.d(TAG, "Force refresh triggered for id: %d", which);
  
      Bundle options = new Bundle();
      options.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
      options.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
      options.putInt(SYNC.ARG, which);
  
      ContentResolver.requestSync(
              AuthenticatorService.getAccount(ACCOUNT_TYPE),
              DataProvider.AUTHORITY,
              options
      );
  }
  </code></pre>
  
  <p>Has anyone encountered similar problems or an idea of what I made wrong?</p>
  
  <hr>
  
  <h2>Update 1</h2>
  
  <p>I tried changing the way I use the <code>SyncStatusObserver</code>. I now get the information from the <code>which</code> flag parameter like so:</p>
  
  <pre><code>private final SyncStatusObserver syncStatusObserver = new SyncStatusObserver() {
      @Override
      public void onStatusChanged(int which) {
          boolean syncActive = (which &amp; ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE) == ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE;
          boolean syncPending = (which &amp; ContentResolver.SYNC_OBSERVER_TYPE_PENDING) == ContentResolver.SYNC_OBSERVER_TYPE_PENDING;
  
          boolean refreshing = syncActive || syncPending;
          // update UI...
      }
  };
  </code></pre>
  
  <p>When I do this, the <code>pending</code> state seems to be correct, so its returning <code>false</code> as soon as the adapter is starting the sync process, <strong>but</strong> now the adapter stays active all the time and I have the same wrong result for <code>boolean refreshing</code> as always. :/</p>