I want to show a ProgressBar in the ActionBar while my SyncAdapter is actively synchronizing content to and from the web.
I have tried using the SyncStatusObserver together with ContentProvider.addStatusChangeListener. However, I cannot check if a SyncAdapter is actively running. I can only check:
- SyncAdapter is pending using ContentResolver.isSyncPending
- SyncAdapter is pending OR actively working using ContentResolver.isSyncActive
These flags can be combined: !isSyncPending && isSyncActive
so that it is possible to check that a SyncAdapter is actively working and does not have any pending work. However, in some cases the SyncAdapter is actively working AND have a second pending request waiting for it.
It seems so simple but I can't find a way around this problem. Having the ProgressBar visible when the SyncAdapter is not running is giving users the impression that the synchronization is very slow. Having it not show the ProgressBar makes the user think nothing is happening.
The above solution in code is shown below. We register the observer in the activity.onResume:
int mask = ContentResolver.SYNC_OBSERVER_TYPE_PENDING | ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE;
syncHandle = ContentResolver.addStatusChangeListener(mask, syncObserver);
The syncObserver is here defined as:
syncObserver = new SyncStatusObserver()
{
@Override
public void onStatusChanged(int which)
{
Account account = getSomeAccount();
boolean syncActive = ContentResolver.isSyncActive(account, CONTENT_AUTHORITY);
boolean syncPending = ContentResolver.isSyncPending(account, CONTENT_AUTHORITY);
boolean isSynchronizing = syncActive && !syncPending;
updateRefreshButtonState();
}
}
I finally found a solution to the problem. The idea is to use the ContentResolver's getCurrentSyncs() or getCurrentSync() methods, whichever is available. The methods below will check if a sync operation is currently working for an account and authority. It requires API level 8 (Froyo = Android 2.2).
private static boolean isSyncActive(Account account, String authority)
{
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
{
return isSyncActiveHoneycomb(account, authority);
} else
{
SyncInfo currentSync = ContentResolver.getCurrentSync();
return currentSync != null && currentSync.account.equals(account) &&
currentSync.authority.equals(authority);
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private static boolean isSyncActiveHoneycomb(Account account, String authority)
{
for(SyncInfo syncInfo : ContentResolver.getCurrentSyncs())
{
if(syncInfo.account.equals(account) &&
syncInfo.authority.equals(authority))
{
return true;
}
}
return false;
}
An Activity then registers for updates in onResume()
and unregisters in onDestroy()
. Also, one have to update state manually in onResume()
to catch up with current status.
Here is an implementation that does just that. Subclasses should themselves define
- what account to use (implementing
getAccount()
)
- what authoritity to use (the field
CONTENT_AUTHORITY
)
- how to display sychronization status (implementing
updateState(boolean isSynchronizing)
)
I hope it will help someone in the future.
import android.accounts.Account;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.SyncInfo;
import android.content.SyncStatusObserver;
import android.os.Build;
import android.os.Bundle;
public abstract class SyncActivity extends Activity
{
private static final String CONTENT_AUTHORITY = "com.example.authority";
private Object syncHandle;
private SyncStatusObserver observer;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
observer = new SyncStatusObserver()
{
@Override
public void onStatusChanged(int which)
{
runOnUiThread(new Runnable()
{
@Override
public void run()
{
Account account = getAccount();
boolean isSynchronizing =
isSyncActive(account, CONTENT_AUTHORITY);
updateState(isSynchronizing);
}
});
}
};
}
@Override
protected void onResume()
{
super.onResume();
// Refresh synchronization status
observer.onStatusChanged(0);
// Watch for synchronization status changes
final int mask = ContentResolver.SYNC_OBSERVER_TYPE_PENDING |
ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE;
syncHandle = ContentResolver.addStatusChangeListener(mask, observer);
}
@Override
protected void onPause()
{
super.onPause();
// Remove our synchronization listener if registered
if (syncHandle != null)
{
ContentResolver.removeStatusChangeListener(syncHandle);
syncHandle = null;
}
}
private static boolean isSyncActive(Account account, String authority)
{
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
{
return isSyncActiveHoneycomb(account, authority);
} else
{
SyncInfo currentSync = ContentResolver.getCurrentSync();
return currentSync != null && currentSync.account.equals(account)
&& currentSync.authority.equals(authority);
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private static boolean isSyncActiveHoneycomb(Account account,
String authority)
{
for(SyncInfo syncInfo : ContentResolver.getCurrentSyncs())
{
if(syncInfo.account.equals(account) &&
syncInfo.authority.equals(authority))
{
return true;
}
}
return false;
}
protected abstract Account getAccount();
protected abstract void updateState(boolean isSynchronizing);
}