How to know when sync is finished?
Asked Answered
A

3

31

I have implemented a sync adapter and I want to get a callback when it finishes in my activity. I have tried using ContentResolver.addStatusChangeListener, but I am only getting callbacks when the sync is pending / active. Here's some relevant code from my activity:

@Override
protected void onResume() {
    super.onResume();
    final int mask = ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE | ContentResolver.SYNC_OBSERVER_TYPE_PENDING;
    syncObserverHandle = ContentResolver.addStatusChangeListener(mask, syncStatusObserver);
}

@Override
protected void onPause() {
    super.onPause();
    if (syncObserverHandle != null) {
        ContentResolver.removeStatusChangeListener(syncObserverHandle);
        syncObserverHandle = null;
    }
}

private SyncStatusObserver syncStatusObserver = new SyncStatusObserver() {

    @Override
    public void onStatusChanged(int which) {
        AccountManager am = AccountManager.get(TodosActivity.this);
        Account a = am.getAccountsByType(Const.ACCOUNT_TYPE)[0];

        Log.d(Const.TAG, "Sync status changed: " + which);

        if (!ContentResolver.isSyncActive(a, DataProvider.AUTHORITY) &&
                !ContentResolver.isSyncPending(a, DataProvider.AUTHORITY)) {
            Log.d(Const.TAG, "Sync finished, should refresh nao!!");
        }
    }
};

However, the if in the onStatusChanged method is never valid. I have taken this example from the JumpNote demo where it works probably because it is also called manually in onResume(), so it's probably never called by the system when the sync is finished. Or is it, and I'm doing something wrong? Here's what I get in logcat:

D/MYAPP (10903): Sync status changed: 2
D/MYAPP (10903): Sync status changed: 2
D/MYAPP (10903): Sync status changed: 4
D/MYAPP (10981): --> DataSyncAdapter.onPerformSync()
D/MYAPP (10981): <-- DataSyncAdapter.onPerformSync()
D/MYAPP (10903): Sync status changed: 4

So, it seems that I could rely on the second SYNC_OBSERVER_TYPE_ACTIVE (4) status change to refresh my data, but that seems really ugly. Any ideas?

Archimandrite answered 8/7, 2011 at 9:13 Comment(0)
A
42

One solution that I have found is to ditch the ContentResolver completely, and implement my own broadcast. Basically, add this in the sync adapter, at the end of onPerformSync:

Intent i = new Intent(SYNC_FINISHED);
sendBroadcast(i);

And this in the activity:

@Override
protected void onResume() {
    super.onResume();
    registerReceiver(syncFinishedReceiver, new IntentFilter(DataSyncService.SYNC_FINISHED));
}

@Override
protected void onPause() {
    super.onPause();
    unregisterReceiver(syncFinishedReceiver);
}

private BroadcastReceiver syncFinishedReceiver = new BroadcastReceiver() {

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(Const.TAG, "Sync finished, should refresh nao!!");
    }
};

This seems to work just fine, however I was hoping to find something in the SDK that directly notify me when a sync is finished.

Archimandrite answered 8/7, 2011 at 10:2 Comment(4)
+1 I've lost one hour with ContentResolver.isSyncActive. Doing it myself was actually much easier.Beggary
When you say: "add this in the sync service" do you actually mean sync adapter? "...at the end" is making me think: once the sync completes at the end of onPerformSync.Truehearted
I not understand this, what's sendBroadcast and SYNC_FINISHED?Awlwort
Check this answer for a better description: #9434174Viscounty
A
17

Strange, it does work for me.

In my Activity I have:

  @Override
  protected void onPause() {
    super.onPause();
    ContentResolver.removeStatusChangeListener(mContentProviderHandle);
  }

  @Override
  protected void onResume() {
    super.onResume();
    mContentProviderHandle = ContentResolver.addStatusChangeListener(
        ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE, this);
  }

  @Override
  public void onStatusChanged(int which) {
    AccountManager accountManager = AccountManager.get(this);
    Account[] accounts = accountManager
        .getAccountsByType(AuthenticatorActivity.PARAM_ACCOUNT_TYPE);

    if (accounts.length <= 0) {
      return;
    }

    updateRefresh(ContentResolver.isSyncActive(accounts[0],
        MyContentProvider.AUTHORITY));
  }

  // Since onStatusChanged() is not called from the main thread
  // I need to update the ui in the ui-thread.
  private void updateRefresh(final boolean isSyncing) {
    runOnUiThread(new Runnable() {

      @Override
      public void run() {
        if (isSyncing) {
          mRefreshMenu.setActionView(R.layout.menu_item_refresh);
        } else {
          mRefreshMenu.setActionView(null);
        }
      }
    });
  }
Annuitant answered 25/7, 2012 at 19:40 Comment(3)
this worked for me , though i had to add 2 permissions: GET_ACCOUNTS , READ_SYNC_STATSEvacuation
@Override public void onStatusChanged >> "Method does not override method from its superclass". ??Krell
Btw, I don't think there's any point in adding and removing the listener when the activity is paused and resumed. In fact, it only complicates the logic and leaves this example with a bug: If the sync status changes while it's paused, the UI will never indicate it. For this code to be correct you need to check the sync status on every onResume in case it updated while it was paused. But this is unnecessary. It's safe to update the UI while paused, you just shouldn't be running animations or doing operations that continually consume CPU.Tranquilize
E
6

I was working on something similar, and I have one more Log.d at the end of the onStatusChanged function, and it wasn't executing!

So after 15 minutes of debugging and trial on error, I realize that I need to add the READ_SYNC_STATS permission, I already have the GET_ACCOUNTS permission though. So, please check that you get the right permission, its not that the "if" doesn't becomes true, its that it bails all the time.

Ewald answered 29/12, 2012 at 9:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.