diff options
| author | 2010-10-06 10:03:19 -0700 | |
|---|---|---|
| committer | 2010-10-06 10:03:19 -0700 | |
| commit | c8b7971cb3e09dfd4480a4bb8875a32157b62192 (patch) | |
| tree | 36be783cbf78612252ed1333ffc022bea4e56fb0 | |
| parent | 572eab6f17a78a9483bbf4b3646aa5a6038ea210 (diff) | |
| parent | 918339ab8255f8e1d03d8448ab1d9036c7c15173 (diff) | |
Merge "Make a separate active sync queue for initialization and regular syncs."
| -rw-r--r-- | core/java/android/content/SyncManager.java | 932 | ||||
| -rw-r--r-- | core/java/android/content/SyncOperation.java | 73 | ||||
| -rw-r--r-- | core/java/android/content/SyncQueue.java | 91 | ||||
| -rw-r--r-- | core/java/android/content/SyncStorageEngine.java | 102 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/content/SyncQueueTest.java | 164 |
5 files changed, 636 insertions, 726 deletions
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java index 950d339effa8..c9115c57e138 100644 --- a/core/java/android/content/SyncManager.java +++ b/core/java/android/content/SyncManager.java @@ -16,6 +16,18 @@ package android.content; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.Process; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.SystemProperties; +import com.google.android.collect.Lists; import com.google.android.collect.Maps; import com.android.internal.R; @@ -36,17 +48,6 @@ import android.content.pm.ProviderInfo; import android.content.pm.RegisteredServicesCacheListener; import android.net.ConnectivityManager; import android.net.NetworkInfo; -import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.PowerManager; -import android.os.Process; -import android.os.RemoteException; -import android.os.SystemClock; -import android.os.SystemProperties; import android.os.WorkSource; import android.provider.Settings; import android.text.format.DateUtils; @@ -58,11 +59,13 @@ import android.util.Pair; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Random; -import java.util.Collection; import java.util.concurrent.CountDownLatch; /** @@ -81,30 +84,17 @@ public class SyncManager implements OnAccountsUpdateListener { private static final long MAX_TIME_PER_SYNC; static { - String localSyncDelayString = SystemProperties.get("sync.local_sync_delay"); - long localSyncDelay = 30 * 1000; // 30 seconds - if (localSyncDelayString != null) { - try { - localSyncDelay = Long.parseLong(localSyncDelayString); - } catch (NumberFormatException nfe) { - // ignore, use default - } - } - LOCAL_SYNC_DELAY = localSyncDelay; - - String maxTimePerSyncString = SystemProperties.get("sync.max_time_per_sync"); - long maxTimePerSync = 5 * 60 * 1000; // 5 minutes - if (maxTimePerSyncString != null) { - try { - maxTimePerSync = Long.parseLong(maxTimePerSyncString); - } catch (NumberFormatException nfe) { - // ignore, use default - } - } - MAX_TIME_PER_SYNC = maxTimePerSync; + MAX_SIMULTANEOUS_INITIALIZATION_SYNCS = SystemProperties.getInt("sync.max_init_syncs", 5); + MAX_SIMULTANEOUS_REGULAR_SYNCS = SystemProperties.getInt("sync.max_regular_syncs", 2); + LOCAL_SYNC_DELAY = + SystemProperties.getLong("sync.local_sync_delay", 30 * 1000 /* 30 seconds */); + MAX_TIME_PER_SYNC = + SystemProperties.getLong("sync.max_time_per_sync", 5 * 60 * 1000 /* 5 minutes */); + SYNC_NOTIFICATION_DELAY = + SystemProperties.getLong("sync.notification_delay", 30 * 1000 /* 30 seconds */); } - private static final long SYNC_NOTIFICATION_DELAY = 30 * 1000; // 30 seconds + private static final long SYNC_NOTIFICATION_DELAY; /** * When retrying a sync for the first time use this delay. After that @@ -123,21 +113,21 @@ public class SyncManager implements OnAccountsUpdateListener { */ private static final int DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS = 10; - /** - * An error notification is sent if sync of any of the providers has been failing for this long. - */ - private static final long ERROR_NOTIFICATION_DELAY_MS = 1000 * 60 * 10; // 10 minutes - private static final int INITIALIZATION_UNBIND_DELAY_MS = 5000; private static final String SYNC_WAKE_LOCK_PREFIX = "*sync*"; private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm"; + private static final String SYNC_LOOP_WAKE_LOCK = "SyncLoopWakeLock"; + + private static final int MAX_SIMULTANEOUS_REGULAR_SYNCS; + private static final int MAX_SIMULTANEOUS_INITIALIZATION_SYNCS; private Context mContext; private volatile Account[] mAccounts = INITIAL_ACCOUNTS_ARRAY; volatile private PowerManager.WakeLock mHandleAlarmWakeLock; + volatile private PowerManager.WakeLock mSyncManagerWakeLock; volatile private boolean mDataConnectionIsConnected = false; volatile private boolean mStorageIsLow = false; @@ -147,10 +137,8 @@ public class SyncManager implements OnAccountsUpdateListener { private final SyncStorageEngine mSyncStorageEngine; public final SyncQueue mSyncQueue; - private ActiveSyncContext mActiveSyncContext = null; + private final ArrayList<ActiveSyncContext> mActiveSyncContexts = Lists.newArrayList(); - // set if the sync error indicator should be reported. - private boolean mNeedSyncErrorNotification = false; // set if the sync active indicator should be reported private boolean mNeedSyncActiveNotification = false; @@ -200,6 +188,9 @@ public class SyncManager implements OnAccountsUpdateListener { private final PowerManager mPowerManager; + private static final long SYNC_ALARM_TIMEOUT_MIN = 30 * 1000; // 30 seconds + private static final long SYNC_ALARM_TIMEOUT_MAX = 2 * 60 * 60 * 1000; // two hours + public void onAccountsUpdated(Account[] accounts) { // remember if this was the first time this was called after an update final boolean justBootedUp = mAccounts == INITIAL_ACCOUNTS_ARRAY; @@ -207,11 +198,10 @@ public class SyncManager implements OnAccountsUpdateListener { // if a sync is in progress yet it is no longer in the accounts list, // cancel it - ActiveSyncContext activeSyncContext = mActiveSyncContext; - if (activeSyncContext != null) { - if (!ArrayUtils.contains(accounts, activeSyncContext.mSyncOperation.account)) { + for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) { + if (!ArrayUtils.contains(accounts, currentSyncContext.mSyncOperation.account)) { Log.d(TAG, "canceling sync since the account has been removed"); - sendSyncFinishedOrCanceledMessage(activeSyncContext, + sendSyncFinishedOrCanceledMessage(currentSyncContext, null /* no result since this is a cancel */); } } @@ -238,6 +228,7 @@ public class SyncManager implements OnAccountsUpdateListener { // If this was the bootup case then don't sync everything, instead only // sync those that have an unknown syncable state, which will give them // a chance to set their syncable state. + boolean onlyThoseWithUnkownSyncableState = justBootedUp; scheduleSync(null, null, null, 0 /* no delay */, onlyThoseWithUnkownSyncableState); } @@ -371,6 +362,15 @@ public class SyncManager implements OnAccountsUpdateListener { HANDLE_SYNC_ALARM_WAKE_LOCK); mHandleAlarmWakeLock.setReferenceCounted(false); + // This WakeLock is used to ensure that we stay awake while running the sync loop + // message handler. Normally we will hold a sync adapter wake lock while it is being + // synced but during the execution of the sync loop it might finish a sync for + // one sync adapter before starting the sync for the other sync adapter and we + // don't want the device to go to sleep during that window. + mSyncManagerWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + SYNC_LOOP_WAKE_LOCK); + mSyncManagerWakeLock.setReferenceCounted(false); + mSyncStorageEngine.addStatusChangeListener( ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, new ISyncStatusObserver.Stub() { public void onStatusChanged(int which) { @@ -427,8 +427,8 @@ public class SyncManager implements OnAccountsUpdateListener { Intent intent = new Intent(); intent.setAction("android.content.SyncAdapter"); intent.setComponent(syncAdapterInfo.componentName); - if (!mContext.bindService(intent, new InitializerServiceConnection(account, authority, mContext, - mMainHandler), + if (!mContext.bindService(intent, + new InitializerServiceConnection(account, authority, mContext, mMainHandler), Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND)) { Log.w(TAG, "initializeSyncAdapter: failed to bind to " + intent); } @@ -508,8 +508,8 @@ public class SyncManager implements OnAccountsUpdateListener { * @param requestedAccount the account to sync, may be null to signify all accounts * @param requestedAuthority the authority to sync, may be null to indicate all authorities * @param extras a Map of SyncAdapter-specific information to control -* syncs of a specific provider. Can be null. Is ignored -* if the url is null. + * syncs of a specific provider. Can be null. Is ignored + * if the url is null. * @param delay how many milliseconds in the future to wait before performing this * @param onlyThoseWithUnkownSyncableState */ @@ -613,16 +613,29 @@ public class SyncManager implements OnAccountsUpdateListener { continue; } - if (isLoggable) { - Log.v(TAG, "scheduleSync:" - + " delay " + delay - + ", source " + source - + ", account " + account - + ", authority " + authority - + ", extras " + extras); + Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(account, authority); + long delayUntil = mSyncStorageEngine.getDelayUntilTime(account, authority); + final long backoffTime = backoff != null ? backoff.first : 0; + if (isSyncable < 0) { + Bundle newExtras = new Bundle(); + newExtras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true); + scheduleSyncOperation( + new SyncOperation(account, source, authority, newExtras, 0, + backoffTime, delayUntil)); + } + if (!onlyThoseWithUnkownSyncableState) { + if (isLoggable) { + Log.v(TAG, "scheduleSync:" + + " delay " + delay + + ", source " + source + + ", account " + account + + ", authority " + authority + + ", extras " + extras); + } + scheduleSyncOperation( + new SyncOperation(account, source, authority, extras, delay, + backoffTime, delayUntil)); } - scheduleSyncOperation( - new SyncOperation(account, source, authority, extras, delay)); } } } @@ -636,7 +649,8 @@ public class SyncManager implements OnAccountsUpdateListener { } public SyncAdapterType[] getSyncAdapterTypes() { - final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> serviceInfos = + final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> + serviceInfos = mSyncAdapters.getAllServices(); SyncAdapterType[] types = new SyncAdapterType[serviceInfos.size()]; int i = 0; @@ -666,6 +680,14 @@ public class SyncManager implements OnAccountsUpdateListener { mSyncHandler.sendMessage(msg); } + private void sendCancelSyncsMessage(final Account account, final String authority) { + if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CANCEL"); + Message msg = mSyncHandler.obtainMessage(); + msg.what = SyncHandler.MESSAGE_CANCEL; + msg.obj = Pair.create(account, authority); + mSyncHandler.sendMessage(msg); + } + class SyncHandlerMessagePayload { public final ActiveSyncContext activeSyncContext; public final SyncResult syncResult; @@ -683,11 +705,6 @@ public class SyncManager implements OnAccountsUpdateListener { } } - private void clearBackoffSetting(SyncOperation op) { - mSyncStorageEngine.setBackoff(op.account, op.authority, - SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE); - } - private void increaseBackoffSetting(SyncOperation op) { final long now = SystemClock.elapsedRealtime(); @@ -713,6 +730,9 @@ public class SyncManager implements OnAccountsUpdateListener { mSyncStorageEngine.setBackoff(op.account, op.authority, now + newDelayInMs, newDelayInMs); + synchronized (mSyncQueue) { + mSyncQueue.onBackoffChanged(op.account, op.authority, now + newDelayInMs); + } } private void setDelayUntilTime(SyncOperation op, long delayUntilSeconds) { @@ -725,6 +745,9 @@ public class SyncManager implements OnAccountsUpdateListener { newDelayUntilTime = 0; } mSyncStorageEngine.setDelayUntilTime(op.account, op.authority, newDelayUntilTime); + synchronized (mSyncQueue) { + mSyncQueue.onDelayUntilTimeChanged(op.account, op.authority, newDelayUntilTime); + } } /** @@ -733,23 +756,7 @@ public class SyncManager implements OnAccountsUpdateListener { * @param authority limit the cancelations to syncs with this authority, if non-null */ public void cancelActiveSync(Account account, String authority) { - ActiveSyncContext activeSyncContext = mActiveSyncContext; - if (activeSyncContext != null) { - // if an authority was specified then only cancel the sync if it matches - if (account != null) { - if (!account.equals(activeSyncContext.mSyncOperation.account)) { - return; - } - } - // if an account was specified then only cancel the sync if it matches - if (authority != null) { - if (!authority.equals(activeSyncContext.mSyncOperation.authority)) { - return; - } - } - sendSyncFinishedOrCanceledMessage(activeSyncContext, - null /* no result since this is a cancel */); - } + sendCancelSyncsMessage(account, authority); } /** @@ -758,22 +765,6 @@ public class SyncManager implements OnAccountsUpdateListener { * @param syncOperation the SyncOperation to schedule */ public void scheduleSyncOperation(SyncOperation syncOperation) { - // If this operation is expedited and there is a sync in progress then - // reschedule the current operation and send a cancel for it. - final ActiveSyncContext activeSyncContext = mActiveSyncContext; - if (syncOperation.expedited && activeSyncContext != null) { - final boolean hasSameKey = - activeSyncContext.mSyncOperation.key.equals(syncOperation.key); - // This request is expedited and there is a sync in progress. - // Interrupt the current sync only if it is not expedited and if it has a different - // key than the one we are scheduling. - if (!activeSyncContext.mSyncOperation.expedited && !hasSameKey) { - scheduleSyncOperation(new SyncOperation(activeSyncContext.mSyncOperation)); - sendSyncFinishedOrCanceledMessage(activeSyncContext, - null /* no result since this is a cancel */); - } - } - boolean queueChanged; synchronized (mSyncQueue) { queueChanged = mSyncQueue.add(syncOperation); @@ -798,11 +789,11 @@ public class SyncManager implements OnAccountsUpdateListener { * @param authority limit the removals to operations with this authority, if non-null */ public void clearScheduledSyncOperations(Account account, String authority) { - mSyncStorageEngine.setBackoff(account, authority, - SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE); synchronized (mSyncQueue) { mSyncQueue.remove(account, authority); } + mSyncStorageEngine.setBackoff(account, authority, + SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE); } void maybeRescheduleSync(SyncResult syncResult, SyncOperation operation) { @@ -829,7 +820,8 @@ public class SyncManager implements OnAccountsUpdateListener { if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)) { Log.d(TAG, "not retrying sync operation because SYNC_EXTRAS_DO_NOT_RETRY was specified " + operation); - } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)) { + } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false) + && !syncResult.syncAlreadyInProgress) { operation.extras.remove(ContentResolver.SYNC_EXTRAS_UPLOAD); Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync " + "encountered an error: " + operation); @@ -850,7 +842,8 @@ public class SyncManager implements OnAccountsUpdateListener { } scheduleSyncOperation(new SyncOperation(operation.account, operation.syncSource, operation.authority, operation.extras, - DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000)); + DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000, + operation.backoff, operation.delayUntil)); } else if (syncResult.hasSoftError()) { if (isLoggable) { Log.d(TAG, "retrying sync operation because it encountered a soft error: " @@ -873,15 +866,33 @@ public class SyncManager implements OnAccountsUpdateListener { final long mStartTime; long mTimeoutStartTime; boolean mBound; + final PowerManager.WakeLock mSyncWakeLock; + final int mSyncAdapterUid; + SyncInfo mSyncInfo; - public ActiveSyncContext(SyncOperation syncOperation, - long historyRowId) { + /** + * Create an ActiveSyncContext for an impending sync and grab the wakelock for that + * sync adapter. Since this grabs the wakelock you need to be sure to call + * close() when you are done with this ActiveSyncContext, whether the sync succeeded + * or not. + * @param syncOperation the SyncOperation we are about to sync + * @param historyRowId the row in which to record the history info for this sync + * @param syncAdapterUid the UID of the application that contains the sync adapter + * for this sync. This is used to attribute the wakelock hold to that application. + */ + public ActiveSyncContext(SyncOperation syncOperation, long historyRowId, + int syncAdapterUid) { super(); + mSyncAdapterUid = syncAdapterUid; mSyncOperation = syncOperation; mHistoryRowId = historyRowId; mSyncAdapter = null; mStartTime = SystemClock.elapsedRealtime(); mTimeoutStartTime = mStartTime; + mSyncWakeLock = mSyncHandler.getSyncWakeLock( + mSyncOperation.account.type, mSyncOperation.authority); + mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterUid)); + mSyncWakeLock.acquire(); } public void sendHeartbeat() { @@ -889,6 +900,7 @@ public class SyncManager implements OnAccountsUpdateListener { } public void onFinished(SyncResult result) { + if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "onFinished: " + this); // include "this" in the message so that the handler can ignore it if this // ActiveSyncContext is no longer the mActiveSyncContext at message handling // time @@ -936,6 +948,10 @@ public class SyncManager implements OnAccountsUpdateListener { return bindResult; } + /** + * Performs the required cleanup, which is the releasing of the wakelock and + * unbinding from the sync adapter (if actually bound). + */ protected void close() { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.d(TAG, "unBindFromSyncAdapter: connection " + this); @@ -944,6 +960,8 @@ public class SyncManager implements OnAccountsUpdateListener { mBound = false; mContext.unbindService(this); } + mSyncWakeLock.setWorkSource(null); + mSyncWakeLock.release(); } @Override @@ -1003,62 +1021,28 @@ public class SyncManager implements OnAccountsUpdateListener { pw.println("no alarm is scheduled (there had better not be any pending syncs)"); } - final SyncManager.ActiveSyncContext activeSyncContext = mActiveSyncContext; - - pw.print("active sync: "); pw.println(activeSyncContext); - pw.print("notification info: "); sb.setLength(0); mSyncHandler.mSyncNotificationInfo.toString(sb); pw.println(sb.toString()); + pw.println(); + pw.println("Active Syncs: " + mActiveSyncContexts.size()); + for (SyncManager.ActiveSyncContext activeSyncContext : mActiveSyncContexts) { + final long durationInSeconds = (now - activeSyncContext.mStartTime) / 1000; + pw.print(" "); + pw.print(DateUtils.formatElapsedTime(durationInSeconds)); + pw.print(" - "); + pw.print(activeSyncContext.mSyncOperation.dump(false)); + pw.println(); + } + synchronized (mSyncQueue) { - pw.print("sync queue: "); sb.setLength(0); mSyncQueue.dump(sb); - pw.println(sb.toString()); - } - - SyncInfo active = mSyncStorageEngine.getCurrentSync(); - if (active != null) { - SyncStorageEngine.AuthorityInfo authority - = mSyncStorageEngine.getAuthority(active.authorityId); - final long durationInSeconds = (now - active.startTime) / 1000; - pw.print("Active sync: "); - pw.print(authority != null ? authority.account : "<no account>"); - pw.print(" "); - pw.print(authority != null ? authority.authority : "<no account>"); - if (activeSyncContext != null) { - pw.print(" "); - pw.print(SyncStorageEngine.SOURCES[ - activeSyncContext.mSyncOperation.syncSource]); - } - pw.print(", duration is "); - pw.println(DateUtils.formatElapsedTime(durationInSeconds)); - } else { - pw.println("No sync is in progress."); - } - - ArrayList<SyncStorageEngine.PendingOperation> ops - = mSyncStorageEngine.getPendingOperations(); - if (ops != null && ops.size() > 0) { - pw.println(); - pw.println("Pending Syncs"); - final int N = ops.size(); - for (int i=0; i<N; i++) { - SyncStorageEngine.PendingOperation op = ops.get(i); - pw.print(" #"); pw.print(i); pw.print(": account="); - pw.print(op.account.name); pw.print(":"); - pw.print(op.account.type); pw.print(" authority="); - pw.print(op.authority); pw.print(" expedited="); - pw.println(op.expedited); - if (op.extras != null && op.extras.size() > 0) { - sb.setLength(0); - SyncOperation.extrasToStringBuilder(op.extras, sb, false /* asKey */); - pw.print(" extras: "); pw.println(sb.toString()); - } - } } + pw.println(); + pw.print(sb.toString()); // join the installed sync adapter with the accounts list and emit for everything pw.println(); @@ -1261,7 +1245,7 @@ public class SyncManager implements OnAccountsUpdateListener { /** Call to let the tracker know that the sync state may have changed */ public synchronized void update() { - final boolean isSyncInProgress = mActiveSyncContext != null; + final boolean isSyncInProgress = !mActiveSyncContexts.isEmpty(); if (isSyncInProgress == mLastWasSyncing) return; final long now = SystemClock.elapsedRealtime(); if (isSyncInProgress) { @@ -1301,17 +1285,14 @@ public class SyncManager implements OnAccountsUpdateListener { private static final int MESSAGE_CHECK_ALARMS = 3; private static final int MESSAGE_SERVICE_CONNECTED = 4; private static final int MESSAGE_SERVICE_DISCONNECTED = 5; + private static final int MESSAGE_CANCEL = 6; public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo(); private Long mAlarmScheduleTime = null; public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker(); - private PowerManager.WakeLock mSyncWakeLock; - private final HashMap<Pair<String, String>, PowerManager.WakeLock> mWakeLocks = + private final HashMap<Pair<String, String>, PowerManager.WakeLock> mWakeLocks = Maps.newHashMap(); - // used to track if we have installed the error notification so that we don't reinstall - // it if sync is still failing - private boolean mErrorNotificationInstalled = false; private volatile CountDownLatch mReadyToRunLatch = new CountDownLatch(1); public void onBootCompleted() { mBootCompleted = true; @@ -1351,12 +1332,6 @@ public class SyncManager implements OnAccountsUpdateListener { * Used to keep track of whether a sync notification is active and who it is for. */ class SyncNotificationInfo { - // only valid if isActive is true - public Account account; - - // only valid if isActive is true - public String authority; - // true iff the notification manager has been asked to send the notification public boolean isActive = false; @@ -1365,10 +1340,7 @@ public class SyncManager implements OnAccountsUpdateListener { public Long startTime = null; public void toString(StringBuilder sb) { - sb.append("account ").append(account) - .append(", authority ").append(authority) - .append(", isActive ").append(isActive) - .append(", startTime ").append(startTime); + sb.append("isActive ").append(isActive).append(", startTime ").append(startTime); } @Override @@ -1384,60 +1356,72 @@ public class SyncManager implements OnAccountsUpdateListener { } public void handleMessage(Message msg) { - Long earliestFuturePollTime = null; + long earliestFuturePollTime = Long.MAX_VALUE; + long nextPendingSyncTime = Long.MAX_VALUE; try { waitUntilReadyToRun(); + mSyncManagerWakeLock.acquire(); // Always do this first so that we be sure that any periodic syncs that // are ready to run have been converted into pending syncs. This allows the // logic that considers the next steps to take based on the set of pending syncs // to also take into account the periodic syncs. earliestFuturePollTime = scheduleReadyPeriodicSyncs(); switch (msg.what) { + case SyncHandler.MESSAGE_CANCEL: { + Pair<Account, String> payload = (Pair<Account, String>)msg.obj; + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CANCEL: " + + payload.first + ", " + payload.second); + } + cancelActiveSyncLocked(payload.first, payload.second); + nextPendingSyncTime = maybeStartNextSyncLocked(); + break; + } + case SyncHandler.MESSAGE_SYNC_FINISHED: if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_FINISHED"); } SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload)msg.obj; - if (mActiveSyncContext != payload.activeSyncContext) { - Log.d(TAG, "handleSyncHandlerMessage: sync context doesn't match, " - + "dropping: mActiveSyncContext " + mActiveSyncContext - + " != " + payload.activeSyncContext); - return; + if (!isSyncStillActive(payload.activeSyncContext)) { + Log.d(TAG, "handleSyncHandlerMessage: dropping since the " + + "sync is no longer active: " + + payload.activeSyncContext); + break; } - runSyncFinishedOrCanceled(payload.syncResult); + runSyncFinishedOrCanceledLocked(payload.syncResult, payload.activeSyncContext); - // since we are no longer syncing, check if it is time to start a new sync - runStateIdle(); + // since a sync just finished check if it is time to start a new sync + nextPendingSyncTime = maybeStartNextSyncLocked(); break; case SyncHandler.MESSAGE_SERVICE_CONNECTED: { ServiceConnectionData msgData = (ServiceConnectionData)msg.obj; if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CONNECTED: " - + msgData.activeSyncContext - + " active is " + mActiveSyncContext); + + msgData.activeSyncContext); } // check that this isn't an old message - if (mActiveSyncContext == msgData.activeSyncContext) { - runBoundToSyncAdapter(msgData.syncAdapter); + if (isSyncStillActive(msgData.activeSyncContext)) { + runBoundToSyncAdapter(msgData.activeSyncContext, msgData.syncAdapter); } break; } case SyncHandler.MESSAGE_SERVICE_DISCONNECTED: { - ServiceConnectionData msgData = (ServiceConnectionData)msg.obj; + final ActiveSyncContext currentSyncContext = + ((ServiceConnectionData)msg.obj).activeSyncContext; if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_DISCONNECTED: " - + msgData.activeSyncContext - + " active is " + mActiveSyncContext); + + currentSyncContext); } // check that this isn't an old message - if (mActiveSyncContext == msgData.activeSyncContext) { + if (isSyncStillActive(currentSyncContext)) { // cancel the sync if we have a syncadapter, which means one is // outstanding - if (mActiveSyncContext.mSyncAdapter != null) { + if (currentSyncContext.mSyncAdapter != null) { try { - mActiveSyncContext.mSyncAdapter.cancelSync(mActiveSyncContext); + currentSyncContext.mSyncAdapter.cancelSync(currentSyncContext); } catch (RemoteException e) { // we don't need to retry this in this case } @@ -1447,11 +1431,10 @@ public class SyncManager implements OnAccountsUpdateListener { // which is a soft error SyncResult syncResult = new SyncResult(); syncResult.stats.numIoExceptions++; - runSyncFinishedOrCanceled(syncResult); + runSyncFinishedOrCanceledLocked(syncResult, currentSyncContext); - // since we are no longer syncing, check if it is time to start a new - // sync - runStateIdle(); + // since a sync just finished check if it is time to start a new sync + nextPendingSyncTime = maybeStartNextSyncLocked(); } break; @@ -1464,22 +1447,7 @@ public class SyncManager implements OnAccountsUpdateListener { } mAlarmScheduleTime = null; try { - if (mActiveSyncContext != null) { - if (isLoggable) { - Log.v(TAG, "handleSyncHandlerMessage: sync context is active"); - } - runStateSyncing(); - } - - // if the above call to runStateSyncing() resulted in the end of a sync, - // check if it is time to start a new sync - if (mActiveSyncContext == null) { - if (isLoggable) { - Log.v(TAG, "handleSyncHandlerMessage: " - + "sync context is not active"); - } - runStateIdle(); - } + nextPendingSyncTime = maybeStartNextSyncLocked(); } finally { mHandleAlarmWakeLock.release(); } @@ -1490,19 +1458,14 @@ public class SyncManager implements OnAccountsUpdateListener { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_CHECK_ALARMS"); } - // we do all the work for this case in the finally block + nextPendingSyncTime = maybeStartNextSyncLocked(); break; } } finally { - final boolean isSyncInProgress = mActiveSyncContext != null; - if (!isSyncInProgress && mSyncWakeLock != null) { - mSyncWakeLock.release(); - mSyncWakeLock = null; - } - manageSyncNotification(); - manageErrorNotification(); - manageSyncAlarm(earliestFuturePollTime); + manageSyncNotificationLocked(); + manageSyncAlarmLocked(earliestFuturePollTime, nextPendingSyncTime); mSyncTimeTracker.update(); + mSyncManagerWakeLock.release(); } } @@ -1511,10 +1474,10 @@ public class SyncManager implements OnAccountsUpdateListener { * @return the desired start time of the earliest future periodic sync operation, * in milliseconds since boot */ - private Long scheduleReadyPeriodicSyncs() { + private long scheduleReadyPeriodicSyncs() { final boolean backgroundDataUsageAllowed = getConnectivityManager().getBackgroundDataSetting(); - Long earliestFuturePollTime = null; + long earliestFuturePollTime = Long.MAX_VALUE; if (!backgroundDataUsageAllowed || !mSyncStorageEngine.getMasterSyncAutomatically()) { return earliestFuturePollTime; } @@ -1544,23 +1507,27 @@ public class SyncManager implements OnAccountsUpdateListener { long nextPollTimeAbsolute = lastPollTimeAbsolute + periodInSeconds * 1000; // if it is ready to run then schedule it and mark it as having been scheduled if (nextPollTimeAbsolute <= nowAbsolute) { + final Pair<Long, Long> backoff = + mSyncStorageEngine.getBackoff(info.account, info.authority); scheduleSyncOperation( new SyncOperation(info.account, SyncStorageEngine.SOURCE_PERIODIC, - info.authority, extras, 0 /* delay */)); + info.authority, extras, 0 /* delay */, + backoff != null ? backoff.first : 0, + mSyncStorageEngine.getDelayUntilTime( + info.account, info.authority))); status.setPeriodicSyncTime(i, nowAbsolute); } else { // it isn't ready to run, remember this time if it is earlier than // earliestFuturePollTime - if (earliestFuturePollTime == null - || nextPollTimeAbsolute < earliestFuturePollTime) { + if (nextPollTimeAbsolute < earliestFuturePollTime) { earliestFuturePollTime = nextPollTimeAbsolute; } } } } - if (earliestFuturePollTime == null) { - return null; + if (earliestFuturePollTime == Long.MAX_VALUE) { + return Long.MAX_VALUE; } // convert absolute time to elapsed time @@ -1570,47 +1537,23 @@ public class SyncManager implements OnAccountsUpdateListener { : (earliestFuturePollTime - nowAbsolute)); } - private void runStateSyncing() { - // if the sync timeout has been reached then cancel it - ActiveSyncContext activeSyncContext = mActiveSyncContext; - - final long now = SystemClock.elapsedRealtime(); - if (now > activeSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC) { - Pair<SyncOperation, Long> nextOpAndRunTime; - synchronized (mSyncQueue) { - nextOpAndRunTime = mSyncQueue.nextOperation(); - } - if (nextOpAndRunTime != null && nextOpAndRunTime.second <= now) { - Log.d(TAG, "canceling and rescheduling sync because it ran too long: " - + activeSyncContext.mSyncOperation); - scheduleSyncOperation(new SyncOperation(activeSyncContext.mSyncOperation)); - sendSyncFinishedOrCanceledMessage(activeSyncContext, - null /* no result since this is a cancel */); - } else { - activeSyncContext.mTimeoutStartTime = now + MAX_TIME_PER_SYNC; - } - } - - // no need to schedule an alarm, as that will be done by our caller. - } - - private void runStateIdle() { - boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); - if (isLoggable) Log.v(TAG, "runStateIdle"); + private long maybeStartNextSyncLocked() { + final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); + if (isLoggable) Log.v(TAG, "maybeStartNextSync"); // If we aren't ready to run (e.g. the data connection is down), get out. if (!mDataConnectionIsConnected) { if (isLoggable) { - Log.v(TAG, "runStateIdle: no data connection, skipping"); + Log.v(TAG, "maybeStartNextSync: no data connection, skipping"); } - return; + return Long.MAX_VALUE; } if (mStorageIsLow) { if (isLoggable) { - Log.v(TAG, "runStateIdle: memory low, skipping"); + Log.v(TAG, "maybeStartNextSync: memory low, skipping"); } - return; + return Long.MAX_VALUE; } // If the accounts aren't known yet then we aren't ready to run. We will be kicked @@ -1618,46 +1561,56 @@ public class SyncManager implements OnAccountsUpdateListener { Account[] accounts = mAccounts; if (accounts == INITIAL_ACCOUNTS_ARRAY) { if (isLoggable) { - Log.v(TAG, "runStateIdle: accounts not known, skipping"); + Log.v(TAG, "maybeStartNextSync: accounts not known, skipping"); } - return; + return Long.MAX_VALUE; } // Otherwise consume SyncOperations from the head of the SyncQueue until one is // found that is runnable (not disabled, etc). If that one is ready to run then // start it, otherwise just get out. - SyncOperation op; - int syncableState; final boolean backgroundDataUsageAllowed = getConnectivityManager().getBackgroundDataSetting(); final boolean masterSyncAutomatically = mSyncStorageEngine.getMasterSyncAutomatically(); - synchronized (mSyncQueue) { - final long now = SystemClock.elapsedRealtime(); - while (true) { - Pair<SyncOperation, Long> nextOpAndRunTime = mSyncQueue.nextOperation(); - if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) { - if (isLoggable) { - Log.v(TAG, "runStateIdle: no more ready sync operations, returning"); - } - return; - } - op = nextOpAndRunTime.first; + final long now = SystemClock.elapsedRealtime(); - // we are either going to run this sync or drop it so go ahead and - // remove it from the queue now - mSyncQueue.remove(op); + // will be set to the next time that a sync should be considered for running + long nextReadyToRunTime = Long.MAX_VALUE; + + // order the sync queue, dropping syncs that are not allowed + ArrayList<SyncOperation> operations = new ArrayList<SyncOperation>(); + synchronized (mSyncQueue) { + if (isLoggable) { + Log.v(TAG, "build the operation array, syncQueue size is " + + mSyncQueue.mOperationsMap.size()); + } + Iterator<SyncOperation> operationIterator = + mSyncQueue.mOperationsMap.values().iterator(); + while (operationIterator.hasNext()) { + final SyncOperation op = operationIterator.next(); // drop the sync if the account of this operation no longer exists if (!ArrayUtils.contains(mAccounts, op.account)) { + operationIterator.remove(); + mSyncStorageEngine.deleteFromPending(op.pendingOperation); continue; } - - // drop this sync request if it isn't syncable, intializing the sync adapter - // if the syncable state is set to "unknown" - syncableState = mSyncStorageEngine.getIsSyncable(op.account, op.authority); + // drop this sync request if it isn't syncable + int syncableState = mSyncStorageEngine.getIsSyncable(op.account, op.authority); if (syncableState == 0) { + operationIterator.remove(); + mSyncStorageEngine.deleteFromPending(op.pendingOperation); + continue; + } + + // if the next run time is in the future, meaning there are no syncs ready + // to run, return the time + if (op.effectiveRunTime > now) { + if (nextReadyToRunTime > op.effectiveRunTime) { + nextReadyToRunTime = op.effectiveRunTime; + } continue; } @@ -1669,30 +1622,139 @@ public class SyncManager implements OnAccountsUpdateListener { || !backgroundDataUsageAllowed || !mSyncStorageEngine.getSyncAutomatically( op.account, op.authority))) { + operationIterator.remove(); + mSyncStorageEngine.deleteFromPending(op.pendingOperation); continue; } - // go ahead and try to sync this syncOperation - break; + operations.add(op); + } + } + + // find the next operation to dispatch, if one is ready + // iterate from the top, keep issuing (while potentially cancelling existing syncs) + // until the quotas are filled. + // once the quotas are filled iterate once more to find when the next one would be + // (also considering pre-emption reasons). + if (isLoggable) Log.v(TAG, "sort the candidate operations, size " + operations.size()); + Collections.sort(operations); + if (isLoggable) Log.v(TAG, "dispatch all ready sync operations"); + for (int i = 0, N = operations.size(); i < N; i++) { + final SyncOperation candidate = operations.get(i); + final boolean candidateIsInitialization = candidate.isInitialization(); + + int numInit = 0; + int numRegular = 0; + ActiveSyncContext conflict = null; + ActiveSyncContext longRunning = null; + ActiveSyncContext toReschedule = null; + + for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) { + final SyncOperation activeOp = activeSyncContext.mSyncOperation; + if (activeOp.isInitialization()) { + numInit++; + } else { + numRegular++; + } + if (activeOp.account.type.equals(candidate.account.type) + && activeOp.authority.equals(candidate.authority)) { + conflict = activeSyncContext; + // don't break out since we want to do a full count of the varieties + } else { + if (candidateIsInitialization == activeOp.isInitialization() + && activeSyncContext.mStartTime + MAX_TIME_PER_SYNC < now) { + longRunning = activeSyncContext; + // don't break out since we want to do a full count of the varieties + } + } } - // We will do this sync. Run it outside of the synchronized block. if (isLoggable) { - Log.v(TAG, "runStateIdle: we are going to sync " + op); + Log.v(TAG, "candidate " + (i + 1) + " of " + N + ": " + candidate); + Log.v(TAG, " numActiveInit=" + numInit + ", numActiveRegular=" + numRegular); + Log.v(TAG, " longRunning: " + longRunning); + Log.v(TAG, " conflict: " + conflict); } + + if (conflict != null) { + if (candidateIsInitialization && !conflict.mSyncOperation.isInitialization() + && numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS) { + toReschedule = conflict; + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "canceling and rescheduling sync since an initialization " + + "takes higher priority, " + conflict); + } + } else if (candidate.expedited && !conflict.mSyncOperation.expedited + && (candidateIsInitialization + == conflict.mSyncOperation.isInitialization())) { + toReschedule = conflict; + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "canceling and rescheduling sync since an expedited " + + "takes higher priority, " + conflict); + } + } else { + continue; + } + } else { + final boolean roomAvailable = candidateIsInitialization + ? numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS + : numRegular < MAX_SIMULTANEOUS_REGULAR_SYNCS; + if (roomAvailable) { + // dispatch candidate + } else if (longRunning != null + && (candidateIsInitialization + == longRunning.mSyncOperation.isInitialization())) { + toReschedule = longRunning; + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "canceling and rescheduling sync since it ran roo long, " + + longRunning); + } + } else { + continue; + } + } + + if (toReschedule != null) { + runSyncFinishedOrCanceledLocked(null, toReschedule); + scheduleSyncOperation(toReschedule.mSyncOperation); + } + + synchronized (mSyncQueue){ + mSyncQueue.remove(candidate); + } + dispatchSyncOperation(candidate); } - // convert the op into an initialization sync if the syncable state is "unknown" and - // op isn't already an initialization sync. If it is marked syncable then convert - // this into a regular sync - final boolean initializeIsSet = - op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false); - if (syncableState < 0 && !initializeIsSet) { - op.extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true); - op = new SyncOperation(op); - } else if (syncableState > 0 && initializeIsSet) { - op.extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false); - op = new SyncOperation(op); + return nextReadyToRunTime; + } + + private boolean dispatchSyncOperation(SyncOperation op) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "maybeStartNextSync: we are going to sync " + op); + Log.v(TAG, "num active syncs: " + mActiveSyncContexts.size()); + for (ActiveSyncContext syncContext : mActiveSyncContexts) { + Log.v(TAG, syncContext.toString()); + } + } + + // if this is an initialization sync and there is already a sync running with + // the same account type and authority cancel that sync before starting this one + // since otherwise the syncadapter will likely reject this request + if (op.isInitialization()) { + Iterator<ActiveSyncContext> iterator = mActiveSyncContexts.iterator(); + while (iterator.hasNext()) { + ActiveSyncContext syncContext = iterator.next(); + if (!syncContext.mSyncOperation.isInitialization() + && syncContext.mSyncOperation.account.type.equals(op.account.type) + && syncContext.mSyncOperation.authority.equals(op.authority)) { + Log.d(TAG, "canceling and rescheduling " + syncContext.mSyncOperation + + " since we are about to start a sync that used the " + + "same sync adapter, " + op); + iterator.remove(); + runSyncFinishedOrCanceledLocked(null, syncContext); + scheduleSyncOperation(syncContext.mSyncOperation); + } + } } // connect to the sync adapter @@ -1703,79 +1765,70 @@ public class SyncManager implements OnAccountsUpdateListener { Log.d(TAG, "can't find a sync adapter for " + syncAdapterType + ", removing settings for it"); mSyncStorageEngine.removeAuthority(op.account, op.authority); - runStateIdle(); - return; + return false; } ActiveSyncContext activeSyncContext = - new ActiveSyncContext(op, insertStartSyncEvent(op)); - mActiveSyncContext = activeSyncContext; + new ActiveSyncContext(op, insertStartSyncEvent(op), syncAdapterInfo.uid); + activeSyncContext.mSyncInfo = mSyncStorageEngine.addActiveSync(activeSyncContext); + mActiveSyncContexts.add(activeSyncContext); if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "runStateIdle: setting mActiveSyncContext to " + mActiveSyncContext); + Log.v(TAG, "dispatchSyncOperation: starting " + activeSyncContext); } - mSyncStorageEngine.setActiveSync(mActiveSyncContext); if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo)) { Log.e(TAG, "Bind attempt failed to " + syncAdapterInfo); - mActiveSyncContext.close(); - mActiveSyncContext = null; - mSyncStorageEngine.setActiveSync(mActiveSyncContext); - runStateIdle(); - return; + closeActiveSyncContext(activeSyncContext); + return false; } - // Find the wakelock for this account and authority and store it in mSyncWakeLock. - // Be sure to release the previous wakelock so that we don't end up with it being - // held until it is used again. - // There are a couple tricky things about this code: - // - make sure that we acquire the new wakelock before releasing the old one, - // otherwise the device might go to sleep as soon as we release it. - // - since we use non-reference counted wakelocks we have to be sure not to do - // the release if the wakelock didn't change. Othewise we would do an - // acquire followed by a release on the same lock, resulting in no lock - // being held. - PowerManager.WakeLock oldWakeLock = mSyncWakeLock; - try { - mSyncWakeLock = getSyncWakeLock(op.account.type, op.authority); - mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterInfo.uid)); - mSyncWakeLock.acquire(); - } finally { - if (oldWakeLock != null && oldWakeLock != mSyncWakeLock) { - oldWakeLock.release(); - } - } - - // no need to schedule an alarm, as that will be done by our caller. - - // the next step will occur when we get either a timeout or a - // MESSAGE_SERVICE_CONNECTED or MESSAGE_SERVICE_DISCONNECTED message + return true; } - private void runBoundToSyncAdapter(ISyncAdapter syncAdapter) { - mActiveSyncContext.mSyncAdapter = syncAdapter; - final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation; + private void runBoundToSyncAdapter(ActiveSyncContext activeSyncContext, + ISyncAdapter syncAdapter) { + activeSyncContext.mSyncAdapter = syncAdapter; + final SyncOperation syncOperation = activeSyncContext.mSyncOperation; try { - syncAdapter.startSync(mActiveSyncContext, syncOperation.authority, + syncAdapter.startSync(activeSyncContext, syncOperation.authority, syncOperation.account, syncOperation.extras); } catch (RemoteException remoteExc) { - Log.d(TAG, "runStateIdle: caught a RemoteException, rescheduling", remoteExc); - mActiveSyncContext.close(); - mActiveSyncContext = null; - mSyncStorageEngine.setActiveSync(mActiveSyncContext); + Log.d(TAG, "maybeStartNextSync: caught a RemoteException, rescheduling", remoteExc); + closeActiveSyncContext(activeSyncContext); increaseBackoffSetting(syncOperation); scheduleSyncOperation(new SyncOperation(syncOperation)); } catch (RuntimeException exc) { - mActiveSyncContext.close(); - mActiveSyncContext = null; - mSyncStorageEngine.setActiveSync(mActiveSyncContext); + closeActiveSyncContext(activeSyncContext); Log.e(TAG, "Caught RuntimeException while starting the sync " + syncOperation, exc); } } - private void runSyncFinishedOrCanceled(SyncResult syncResult) { + private void cancelActiveSyncLocked(Account account, String authority) { + ArrayList<ActiveSyncContext> activeSyncs = + new ArrayList<ActiveSyncContext>(mActiveSyncContexts); + for (ActiveSyncContext activeSyncContext : activeSyncs) { + if (activeSyncContext != null) { + // if an authority was specified then only cancel the sync if it matches + if (account != null) { + if (!account.equals(activeSyncContext.mSyncOperation.account)) { + return; + } + } + // if an account was specified then only cancel the sync if it matches + if (authority != null) { + if (!authority.equals(activeSyncContext.mSyncOperation.authority)) { + return; + } + } + runSyncFinishedOrCanceledLocked(null /* no result since this is a cancel */, + activeSyncContext); + } + } + } + + private void runSyncFinishedOrCanceledLocked(SyncResult syncResult, + ActiveSyncContext activeSyncContext) { boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); - final ActiveSyncContext activeSyncContext = mActiveSyncContext; - mActiveSyncContext = null; - mSyncStorageEngine.setActiveSync(mActiveSyncContext); + closeActiveSyncContext(activeSyncContext); final SyncOperation syncOperation = activeSyncContext.mSyncOperation; @@ -1795,16 +1848,6 @@ public class SyncManager implements OnAccountsUpdateListener { // TODO: set these correctly when the SyncResult is extended to include it downstreamActivity = 0; upstreamActivity = 0; - clearBackoffSetting(syncOperation); - // if this was an initialization sync and the sync adapter is now - // marked syncable then reschedule the sync. The next time it runs it - // will be made into a regular sync. - if (syncOperation.extras.getBoolean( - ContentResolver.SYNC_EXTRAS_INITIALIZE, false) - && mSyncStorageEngine.getIsSyncable( - syncOperation.account, syncOperation.authority) > 0) { - scheduleSyncOperation(new SyncOperation(syncOperation)); - } } else { Log.d(TAG, "failed sync operation " + syncOperation + ", " + syncResult); // the operation failed so increase the backoff time @@ -1839,8 +1882,6 @@ public class SyncManager implements OnAccountsUpdateListener { stopSyncEvent(activeSyncContext.mHistoryRowId, syncOperation, historyMessage, upstreamActivity, downstreamActivity, elapsedTime); - activeSyncContext.close(); - if (syncResult != null && syncResult.tooManyDeletions) { installHandleTooManyDeletesNotification(syncOperation.account, syncOperation.authority, syncResult.stats.numDeletes); @@ -1851,11 +1892,18 @@ public class SyncManager implements OnAccountsUpdateListener { if (syncResult != null && syncResult.fullSyncRequested) { scheduleSyncOperation(new SyncOperation(syncOperation.account, - syncOperation.syncSource, syncOperation.authority, new Bundle(), 0)); + syncOperation.syncSource, syncOperation.authority, new Bundle(), 0, + syncOperation.backoff, syncOperation.delayUntil)); } // no need to schedule an alarm, as that will be done by our caller. } + private void closeActiveSyncContext(ActiveSyncContext activeSyncContext) { + activeSyncContext.close(); + mActiveSyncContexts.remove(activeSyncContext); + mSyncStorageEngine.removeActiveSync(activeSyncContext.mSyncInfo); + } + /** * Convert the error-containing SyncResult into the Sync.History error number. Since * the SyncResult may indicate multiple errors at once, this method just returns the @@ -1885,11 +1933,11 @@ public class SyncManager implements OnAccountsUpdateListener { throw new IllegalStateException("we are not in an error state, " + syncResult); } - private void manageSyncNotification() { + private void manageSyncNotificationLocked() { boolean shouldCancel; boolean shouldInstall; - if (mActiveSyncContext == null) { + if (mActiveSyncContexts.isEmpty()) { mSyncNotificationInfo.startTime = null; // we aren't syncing. if the notification is active then remember that we need @@ -1898,34 +1946,38 @@ public class SyncManager implements OnAccountsUpdateListener { shouldInstall = false; } else { // we are syncing - final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation; - final long now = SystemClock.elapsedRealtime(); if (mSyncNotificationInfo.startTime == null) { mSyncNotificationInfo.startTime = now; } - // cancel the notification if it is up and the authority or account is wrong - shouldCancel = mSyncNotificationInfo.isActive && - (!syncOperation.authority.equals(mSyncNotificationInfo.authority) - || !syncOperation.account.equals(mSyncNotificationInfo.account)); - - // there are four cases: - // - the notification is up and there is no change: do nothing - // - the notification is up but we should cancel since it is stale: - // need to install + // there are three cases: + // - the notification is up: do nothing // - the notification is not up but it isn't time yet: don't install // - the notification is not up and it is time: need to install if (mSyncNotificationInfo.isActive) { - shouldInstall = shouldCancel; + shouldInstall = shouldCancel = false; } else { + // it isn't currently up, so there is nothing to cancel + shouldCancel = false; + final boolean timeToShowNotification = now > mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY; - // show the notification immediately if this is a manual sync - final boolean manualSync = syncOperation.extras - .getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false); - shouldInstall = timeToShowNotification || manualSync; + if (timeToShowNotification) { + shouldInstall = true; + } else { + // show the notification immediately if this is a manual sync + shouldInstall = false; + for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) { + final boolean manualSync = activeSyncContext.mSyncOperation.extras + .getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false); + if (manualSync) { + shouldInstall = true; + break; + } + } + } } } @@ -1936,94 +1988,82 @@ public class SyncManager implements OnAccountsUpdateListener { } if (shouldInstall) { - SyncOperation syncOperation = mActiveSyncContext.mSyncOperation; mNeedSyncActiveNotification = true; sendSyncStateIntent(); mSyncNotificationInfo.isActive = true; - mSyncNotificationInfo.account = syncOperation.account; - mSyncNotificationInfo.authority = syncOperation.authority; - } - } - - /** - * Check if there were any long-lasting errors, if so install the error notification, - * otherwise cancel the error notification. - */ - private void manageErrorNotification() { - // - long when = mSyncStorageEngine.getInitialSyncFailureTime(); - if ((when > 0) && (when + ERROR_NOTIFICATION_DELAY_MS < System.currentTimeMillis())) { - if (!mErrorNotificationInstalled) { - mNeedSyncErrorNotification = true; - sendSyncStateIntent(); - } - mErrorNotificationInstalled = true; - } else { - if (mErrorNotificationInstalled) { - mNeedSyncErrorNotification = false; - sendSyncStateIntent(); - } - mErrorNotificationInstalled = false; } } - private void manageSyncAlarm(Long earliestFuturePollElapsedTime) { + private void manageSyncAlarmLocked(long nextPeriodicEventElapsedTime, + long nextPendingEventElapsedTime) { // in each of these cases the sync loop will be kicked, which will cause this // method to be called again if (!mDataConnectionIsConnected) return; if (mStorageIsLow) return; - final long now = SystemClock.elapsedRealtime(); - - // Compute the alarm fire time: - // - not syncing: time of the next sync operation - // - syncing, no notification: time from sync start to notification create time - // - syncing, with notification: time till timeout of the active sync operation - Long alarmTime; - ActiveSyncContext activeSyncContext = mActiveSyncContext; - if (activeSyncContext == null) { - synchronized (mSyncQueue) { - final Pair<SyncOperation, Long> candidate = mSyncQueue.nextOperation(); - if (earliestFuturePollElapsedTime == null && candidate == null) { - alarmTime = null; - } else if (earliestFuturePollElapsedTime == null) { - alarmTime = candidate.second; - } else if (candidate == null) { - alarmTime = earliestFuturePollElapsedTime; - } else { - alarmTime = Math.min(earliestFuturePollElapsedTime, candidate.second); - } + // When the status bar notification should be raised + final long notificationTime = + (!mSyncHandler.mSyncNotificationInfo.isActive + && mSyncHandler.mSyncNotificationInfo.startTime != null) + ? mSyncHandler.mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY + : Long.MAX_VALUE; + + // When we should consider canceling an active sync + long earliestTimeoutTime = Long.MAX_VALUE; + for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) { + final long currentSyncTimeoutTime = + currentSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC; + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "manageSyncAlarm: active sync, mTimeoutStartTime + MAX is " + + currentSyncTimeoutTime); } - } else { - final long notificationTime = - mSyncHandler.mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY; - final long timeoutTime = - mActiveSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC; - if (mSyncHandler.mSyncNotificationInfo.isActive) { - alarmTime = timeoutTime; - } else { - alarmTime = Math.min(notificationTime, timeoutTime); + if (earliestTimeoutTime > currentSyncTimeoutTime) { + earliestTimeoutTime = currentSyncTimeoutTime; } } - // adjust the alarmTime so that we will wake up when it is time to - // install the error notification - if (!mErrorNotificationInstalled) { - long when = mSyncStorageEngine.getInitialSyncFailureTime(); - if (when > 0) { - when += ERROR_NOTIFICATION_DELAY_MS; - // convert when fron absolute time to elapsed run time - long delay = when - System.currentTimeMillis(); - when = now + delay; - alarmTime = alarmTime != null ? Math.min(alarmTime, when) : when; + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "manageSyncAlarm: notificationTime is " + notificationTime); + } + + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "manageSyncAlarm: earliestTimeoutTime is " + earliestTimeoutTime); + } + + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "manageSyncAlarm: nextPeriodicEventElapsedTime is " + + nextPeriodicEventElapsedTime); + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "manageSyncAlarm: nextPendingEventElapsedTime is " + + nextPendingEventElapsedTime); + } + + long alarmTime = Math.min(notificationTime, earliestTimeoutTime); + alarmTime = Math.min(alarmTime, nextPeriodicEventElapsedTime); + alarmTime = Math.min(alarmTime, nextPendingEventElapsedTime); + + // Bound the alarm time. + final long now = SystemClock.elapsedRealtime(); + if (alarmTime < now + SYNC_ALARM_TIMEOUT_MIN) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "manageSyncAlarm: the alarmTime is too small, " + + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN)); + } + alarmTime = now + SYNC_ALARM_TIMEOUT_MIN; + } else if (alarmTime > now + SYNC_ALARM_TIMEOUT_MAX) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "manageSyncAlarm: the alarmTime is too large, " + + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN)); } + alarmTime = now + SYNC_ALARM_TIMEOUT_MAX; } // determine if we need to set or cancel the alarm boolean shouldSet = false; boolean shouldCancel = false; final boolean alarmIsActive = mAlarmScheduleTime != null; - final boolean needAlarm = alarmTime != null; + final boolean needAlarm = alarmTime != Long.MAX_VALUE; if (needAlarm) { if (!alarmIsActive || alarmTime < mAlarmScheduleTime) { shouldSet = true; @@ -2035,6 +2075,11 @@ public class SyncManager implements OnAccountsUpdateListener { // set or cancel the alarm as directed ensureAlarmService(); if (shouldSet) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "requesting that the alarm manager wake us up at elapsed time " + + alarmTime + ", now is " + now + ", " + ((alarmTime - now) / 1000) + + " secs from now"); + } mAlarmScheduleTime = alarmTime; mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, mSyncAlarmIntent); @@ -2048,7 +2093,7 @@ public class SyncManager implements OnAccountsUpdateListener { Intent syncStateIntent = new Intent(Intent.ACTION_SYNC_STATE_CHANGED); syncStateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); syncStateIntent.putExtra("active", mNeedSyncActiveNotification); - syncStateIntent.putExtra("failing", mNeedSyncErrorNotification); + syncStateIntent.putExtra("failing", false); mContext.sendBroadcast(syncStateIntent); } @@ -2137,4 +2182,13 @@ public class SyncManager implements OnAccountsUpdateListener { resultMessage, downstreamActivity, upstreamActivity); } } + + private boolean isSyncStillActive(ActiveSyncContext activeSyncContext) { + for (ActiveSyncContext sync : mActiveSyncContexts) { + if (sync == activeSyncContext) { + return true; + } + } + return false; + } } diff --git a/core/java/android/content/SyncOperation.java b/core/java/android/content/SyncOperation.java index b01608856cc6..3d7f3fbf23ab 100644 --- a/core/java/android/content/SyncOperation.java +++ b/core/java/android/content/SyncOperation.java @@ -33,9 +33,12 @@ public class SyncOperation implements Comparable { public long earliestRunTime; public boolean expedited; public SyncStorageEngine.PendingOperation pendingOperation; + public Long backoff; + public long delayUntil; + public long effectiveRunTime; public SyncOperation(Account account, int source, String authority, Bundle extras, - long delayInMs) { + long delayInMs, long backoff, long delayUntil) { this.account = account; this.syncSource = source; this.authority = authority; @@ -48,6 +51,8 @@ public class SyncOperation implements Comparable { removeFalseExtra(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS); removeFalseExtra(ContentResolver.SYNC_EXTRAS_EXPEDITED); removeFalseExtra(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS); + this.delayUntil = delayUntil; + this.backoff = backoff; final long now = SystemClock.elapsedRealtime(); if (delayInMs < 0) { this.expedited = true; @@ -56,6 +61,7 @@ public class SyncOperation implements Comparable { this.expedited = false; this.earliestRunTime = now + delayInMs; } + updateEffectiveRunTime(); this.key = toKey(); } @@ -72,49 +78,78 @@ public class SyncOperation implements Comparable { this.extras = new Bundle(other.extras); this.expedited = other.expedited; this.earliestRunTime = SystemClock.elapsedRealtime(); + this.backoff = other.backoff; + this.delayUntil = other.delayUntil; + this.updateEffectiveRunTime(); this.key = toKey(); } public String toString() { + return dump(true); + } + + public String dump(boolean useOneLine) { StringBuilder sb = new StringBuilder(); - sb.append("authority: ").append(authority); - sb.append(" account: ").append(account); - sb.append(" extras: "); - extrasToStringBuilder(extras, sb, false /* asKey */); - sb.append(" syncSource: ").append(syncSource); - sb.append(" when: ").append(earliestRunTime); - sb.append(" expedited: ").append(expedited); + sb.append(account.name); + sb.append(" (" + account.type + ")"); + sb.append(", " + authority); + sb.append(", "); + sb.append(SyncStorageEngine.SOURCES[syncSource]); + sb.append(", earliestRunTime " + earliestRunTime); + if (expedited) { + sb.append(", EXPEDITED"); + } + if (!useOneLine && !extras.keySet().isEmpty()) { + sb.append("\n "); + extrasToStringBuilder(extras, sb); + } return sb.toString(); } + public boolean isInitialization() { + return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false); + } + + public boolean ignoreBackoff() { + return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false); + } + private String toKey() { StringBuilder sb = new StringBuilder(); sb.append("authority: ").append(authority); - sb.append(" account {name=" + account.name + ", type=" + account.type + "}"); + sb.append(" account {name=" + account.name + ", type=" + account.type + "}"); sb.append(" extras: "); - extrasToStringBuilder(extras, sb, true /* asKey */); + extrasToStringBuilder(extras, sb); return sb.toString(); } - public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb, boolean asKey) { + public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) { sb.append("["); for (String key : bundle.keySet()) { - // if we are writing this as a key don't consider whether this - // is an initialization sync or not when computing the key since - // we set this flag appropriately when dispatching the sync request. - if (asKey && ContentResolver.SYNC_EXTRAS_INITIALIZE.equals(key)) { - continue; - } sb.append(key).append("=").append(bundle.get(key)).append(" "); } sb.append("]"); } + public void updateEffectiveRunTime() { + effectiveRunTime = ignoreBackoff() + ? earliestRunTime + : Math.max( + Math.max(earliestRunTime, delayUntil), + backoff); + } + public int compareTo(Object o) { SyncOperation other = (SyncOperation)o; - if (earliestRunTime == other.earliestRunTime) { + + if (expedited != other.expedited) { + return expedited ? -1 : 1; + } + + if (effectiveRunTime == other.effectiveRunTime) { return 0; } - return (earliestRunTime < other.earliestRunTime) ? -1 : 1; + + return effectiveRunTime < other.effectiveRunTime ? -1 : 1; } } diff --git a/core/java/android/content/SyncQueue.java b/core/java/android/content/SyncQueue.java index 28baa0da0781..f8261476e704 100644 --- a/core/java/android/content/SyncQueue.java +++ b/core/java/android/content/SyncQueue.java @@ -16,17 +16,18 @@ package android.content; -import com.google.android.collect.Lists; import com.google.android.collect.Maps; +import android.os.SystemClock; +import android.text.format.DateUtils; import android.util.Pair; import android.util.Log; import android.accounts.Account; -import java.util.HashMap; import java.util.ArrayList; -import java.util.Map; +import java.util.HashMap; import java.util.Iterator; +import java.util.Map; /** * @@ -38,7 +39,7 @@ public class SyncQueue { // A Map of SyncOperations operationKey -> SyncOperation that is designed for // quick lookup of an enqueued SyncOperation. - private final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap(); + public final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap(); public SyncQueue(SyncStorageEngine syncStorageEngine) { mSyncStorageEngine = syncStorageEngine; @@ -47,8 +48,11 @@ public class SyncQueue { final int N = ops.size(); for (int i=0; i<N; i++) { SyncStorageEngine.PendingOperation op = ops.get(i); + final Pair<Long, Long> backoff = syncStorageEngine.getBackoff(op.account, op.authority); SyncOperation syncOperation = new SyncOperation( - op.account, op.syncSource, op.authority, op.extras, 0 /* delay */); + op.account, op.syncSource, op.authority, op.extras, 0 /* delay */, + backoff != null ? backoff.first : 0, + syncStorageEngine.getDelayUntilTime(op.account, op.authority)); syncOperation.expedited = op.expedited; syncOperation.pendingOperation = op; add(syncOperation, op); @@ -119,65 +123,26 @@ public class SyncQueue { } } - /** - * Find the operation that should run next. Operations are sorted by their earliestRunTime, - * prioritizing first those with a syncable state of "unknown" that aren't retries then - * expedited operations. - * The earliestRunTime is adjusted by the sync adapter's backoff and delayUntil times, if any. - * @return the operation that should run next and when it should run. The time may be in - * the future. It is expressed in milliseconds since boot. - */ - public Pair<SyncOperation, Long> nextOperation() { - SyncOperation best = null; - long bestRunTime = 0; - boolean bestSyncableIsUnknownAndNotARetry = false; + public void onBackoffChanged(Account account, String providerName, long backoff) { + // for each op that matches the account and provider update its + // backoff and effectiveStartTime for (SyncOperation op : mOperationsMap.values()) { - long opRunTime = op.earliestRunTime; - if (!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) { - Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(op.account, op.authority); - long delayUntil = mSyncStorageEngine.getDelayUntilTime(op.account, op.authority); - opRunTime = Math.max( - Math.max(opRunTime, delayUntil), - backoff != null ? backoff.first : 0); - } - // we know a sync is a retry if the intialization flag is set, since that will only - // be set by the sync dispatching code, thus if it is set it must have already been - // dispatched - final boolean syncableIsUnknownAndNotARetry = - !op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false) - && mSyncStorageEngine.getIsSyncable(op.account, op.authority) < 0; - // if the unsyncable state differs, make the current the best if it is unsyncable - // else, if the expedited state differs, make the current the best if it is expedited - // else, make the current the best if it is earlier than the best - if (best == null - || ((bestSyncableIsUnknownAndNotARetry == syncableIsUnknownAndNotARetry) - ? (best.expedited == op.expedited - ? opRunTime < bestRunTime - : op.expedited) - : syncableIsUnknownAndNotARetry)) { - best = op; - bestSyncableIsUnknownAndNotARetry = syncableIsUnknownAndNotARetry; - bestRunTime = opRunTime; + if (op.account.equals(account) && op.authority.equals(providerName)) { + op.backoff = backoff; + op.updateEffectiveRunTime(); } } - if (best == null) { - return null; - } - return Pair.create(best, bestRunTime); } - /** - * Find and return the SyncOperation that should be run next and is ready to run. - * @param now the current {@link android.os.SystemClock#elapsedRealtime()}, used to - * decide if the sync operation is ready to run - * @return the SyncOperation that should be run next and is ready to run. - */ - public Pair<SyncOperation, Long> nextReadyToRun(long now) { - Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation(); - if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) { - return null; + public void onDelayUntilTimeChanged(Account account, String providerName, long delayUntil) { + // for each op that matches the account and provider update its + // delayUntilTime and effectiveStartTime + for (SyncOperation op : mOperationsMap.values()) { + if (op.account.equals(account) && op.authority.equals(providerName)) { + op.delayUntil = delayUntil; + op.updateEffectiveRunTime(); + } } - return nextOpAndRunTime; } public void remove(Account account, String authority) { @@ -200,9 +165,17 @@ public class SyncQueue { } public void dump(StringBuilder sb) { + final long now = SystemClock.elapsedRealtime(); sb.append("SyncQueue: ").append(mOperationsMap.size()).append(" operation(s)\n"); for (SyncOperation operation : mOperationsMap.values()) { - sb.append(operation).append("\n"); + sb.append(" "); + if (operation.effectiveRunTime <= now) { + sb.append("READY"); + } else { + sb.append(DateUtils.formatElapsedTime((operation.effectiveRunTime - now) / 1000)); + } + sb.append(" - "); + sb.append(operation.dump(false)).append("\n"); } } } diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java index 6413cec32459..487f6ced42a9 100644 --- a/core/java/android/content/SyncStorageEngine.java +++ b/core/java/android/content/SyncStorageEngine.java @@ -229,7 +229,7 @@ public class SyncStorageEngine extends Handler { private final ArrayList<PendingOperation> mPendingOperations = new ArrayList<PendingOperation>(); - private SyncInfo mCurrentSync; + private final ArrayList<SyncInfo> mCurrentSyncs = new ArrayList<SyncInfo>(); private final SparseArray<SyncStatusInfo> mSyncStatus = new SparseArray<SyncStatusInfo>(); @@ -690,23 +690,12 @@ public class SyncStorageEngine extends Handler { /** * Returns true if there is currently a sync operation for the given - * account or authority in the pending list, or actively being processed. + * account or authority actively being processed. */ public boolean isSyncActive(Account account, String authority) { synchronized (mAuthorities) { - int i = mPendingOperations.size(); - while (i > 0) { - i--; - // TODO(fredq): this probably shouldn't be considering - // pending operations. - PendingOperation op = mPendingOperations.get(i); - if (op.account.equals(account) && op.authority.equals(authority)) { - return true; - } - } - - if (mCurrentSync != null) { - AuthorityInfo ainfo = getAuthority(mCurrentSync.authorityId); + for (SyncInfo syncInfo : mCurrentSyncs) { + AuthorityInfo ainfo = getAuthority(syncInfo.authorityId); if (ainfo != null && ainfo.account.equals(account) && ainfo.authority.equals(authority)) { return true; @@ -887,40 +876,47 @@ public class SyncStorageEngine extends Handler { } /** - * Called when the currently active sync is changing (there can only be - * one at a time). Either supply a valid ActiveSyncContext with information - * about the sync, or null to stop the currently active sync. + * Called when a sync is starting. Supply a valid ActiveSyncContext with information + * about the sync. */ - public void setActiveSync(SyncManager.ActiveSyncContext activeSyncContext) { + public SyncInfo addActiveSync(SyncManager.ActiveSyncContext activeSyncContext) { + final SyncInfo syncInfo; synchronized (mAuthorities) { - if (activeSyncContext != null) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "setActiveSync: account=" - + activeSyncContext.mSyncOperation.account - + " auth=" + activeSyncContext.mSyncOperation.authority - + " src=" + activeSyncContext.mSyncOperation.syncSource - + " extras=" + activeSyncContext.mSyncOperation.extras); - } - if (mCurrentSync != null) { - Log.w(TAG, "setActiveSync called with existing active sync!"); - } - AuthorityInfo authority = getAuthorityLocked( - activeSyncContext.mSyncOperation.account, - activeSyncContext.mSyncOperation.authority, - "setActiveSync"); - if (authority == null) { - return; - } - mCurrentSync = new SyncInfo(authority.ident, - authority.account, authority.authority, - activeSyncContext.mStartTime); - } else { - if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "setActiveSync: null"); - mCurrentSync = null; + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "setActiveSync: account=" + + activeSyncContext.mSyncOperation.account + + " auth=" + activeSyncContext.mSyncOperation.authority + + " src=" + activeSyncContext.mSyncOperation.syncSource + + " extras=" + activeSyncContext.mSyncOperation.extras); + } + AuthorityInfo authority = getOrCreateAuthorityLocked( + activeSyncContext.mSyncOperation.account, + activeSyncContext.mSyncOperation.authority, + -1 /* assign a new identifier if creating a new authority */, + true /* write to storage if this results in a change */); + syncInfo = new SyncInfo(authority.ident, + authority.account, authority.authority, + activeSyncContext.mStartTime); + mCurrentSyncs.add(syncInfo); + } + + reportActiveChange(); + return syncInfo; + } + + /** + * Called to indicate that a previously active sync is no longer active. + */ + public void removeActiveSync(SyncInfo syncInfo) { + synchronized (mAuthorities) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "removeActiveSync: account=" + + syncInfo.account + " auth=" + syncInfo.authority); } + mCurrentSyncs.remove(syncInfo); } - reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE); + reportActiveChange(); } /** @@ -1095,10 +1091,26 @@ public class SyncStorageEngine extends Handler { * Return the currently active sync information, or null if there is no * active sync. Note that the returned object is the real, live active * sync object, so be careful what you do with it. + * <p> + * Since multiple concurrent syncs are now supported you should use + * {@link #getCurrentSyncs()} to get the accurate list of current syncs. + * This method returns the first item from the list of current syncs + * or null if there are none. + * @deprecated use {@link #getCurrentSyncs()} */ public SyncInfo getCurrentSync() { synchronized (mAuthorities) { - return mCurrentSync; + return !mCurrentSyncs.isEmpty() ? mCurrentSyncs.get(0) : null; + } + } + + /** + * Return a list of the currently active syncs. Note that the returned items are the + * real, live active sync objects, so be careful what you do with it. + */ + public List<SyncInfo> getCurrentSyncs() { + synchronized (mAuthorities) { + return new ArrayList<SyncInfo>(mCurrentSyncs); } } diff --git a/core/tests/coretests/src/android/content/SyncQueueTest.java b/core/tests/coretests/src/android/content/SyncQueueTest.java deleted file mode 100644 index 1da59d18e1a8..000000000000 --- a/core/tests/coretests/src/android/content/SyncQueueTest.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.content; - -import android.test.AndroidTestCase; -import android.test.RenamingDelegatingContext; -import android.test.mock.MockContext; -import android.test.mock.MockContentResolver; -import android.accounts.Account; -import android.os.Bundle; -import android.os.SystemClock; - -public class SyncQueueTest extends AndroidTestCase { - private static final Account ACCOUNT1 = new Account("test.account1", "test.type1"); - private static final Account ACCOUNT2 = new Account("test.account2", "test.type2"); - private static final String AUTHORITY1 = "test.authority1"; - private static final String AUTHORITY2 = "test.authority2"; - private static final String AUTHORITY3 = "test.authority3"; - - private SyncStorageEngine mSettings; - private SyncQueue mSyncQueue; - - @Override - protected void setUp() throws Exception { - super.setUp(); - MockContentResolver mockResolver = new MockContentResolver(); - mSettings = SyncStorageEngine.newTestInstance(new TestContext(mockResolver, getContext())); - mSyncQueue = new SyncQueue(mSettings); - } - - public void testSyncQueueOrder() throws Exception { - final SyncOperation op1 = new SyncOperation( - ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("1"), 0); - final SyncOperation op2 = new SyncOperation( - ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("2"), 100); - final SyncOperation op3 = new SyncOperation( - ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("3"), 150); - final SyncOperation op4 = new SyncOperation( - ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("4"), 60); - final SyncOperation op5 = new SyncOperation( - ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("5"), 80); - final SyncOperation op6 = new SyncOperation( - ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("6"), 0); - op6.expedited = true; - - mSyncQueue.add(op1); - mSyncQueue.add(op2); - mSyncQueue.add(op3); - mSyncQueue.add(op4); - mSyncQueue.add(op5); - mSyncQueue.add(op6); - - long now = SystemClock.elapsedRealtime() + 200; - - assertEquals(op6, mSyncQueue.nextReadyToRun(now).first); - mSyncQueue.remove(op6); - - assertEquals(op1, mSyncQueue.nextReadyToRun(now).first); - mSyncQueue.remove(op1); - - assertEquals(op4, mSyncQueue.nextReadyToRun(now).first); - mSyncQueue.remove(op4); - - assertEquals(op5, mSyncQueue.nextReadyToRun(now).first); - mSyncQueue.remove(op5); - - assertEquals(op2, mSyncQueue.nextReadyToRun(now).first); - mSyncQueue.remove(op2); - - assertEquals(op3, mSyncQueue.nextReadyToRun(now).first); - mSyncQueue.remove(op3); - } - - public void testOrderWithBackoff() throws Exception { - final SyncOperation op1 = new SyncOperation( - ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("1"), 0); - final SyncOperation op2 = new SyncOperation( - ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("2"), 100); - final SyncOperation op3 = new SyncOperation( - ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("3"), 150); - final SyncOperation op4 = new SyncOperation( - ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY3, newTestBundle("4"), 60); - final SyncOperation op5 = new SyncOperation( - ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("5"), 80); - final SyncOperation op6 = new SyncOperation( - ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("6"), 0); - op6.expedited = true; - - mSyncQueue.add(op1); - mSyncQueue.add(op2); - mSyncQueue.add(op3); - mSyncQueue.add(op4); - mSyncQueue.add(op5); - mSyncQueue.add(op6); - - long now = SystemClock.elapsedRealtime() + 200; - - assertEquals(op6, mSyncQueue.nextReadyToRun(now).first); - mSyncQueue.remove(op6); - - assertEquals(op1, mSyncQueue.nextReadyToRun(now).first); - mSyncQueue.remove(op1); - - mSettings.setBackoff(ACCOUNT2, AUTHORITY3, now + 200, 5); - assertEquals(op5, mSyncQueue.nextReadyToRun(now).first); - - mSettings.setBackoff(ACCOUNT2, AUTHORITY3, SyncStorageEngine.NOT_IN_BACKOFF_MODE, 0); - assertEquals(op4, mSyncQueue.nextReadyToRun(now).first); - - mSettings.setDelayUntilTime(ACCOUNT2, AUTHORITY3, now + 200); - assertEquals(op5, mSyncQueue.nextReadyToRun(now).first); - - mSettings.setDelayUntilTime(ACCOUNT2, AUTHORITY3, 0); - assertEquals(op4, mSyncQueue.nextReadyToRun(now).first); - mSyncQueue.remove(op4); - - assertEquals(op5, mSyncQueue.nextReadyToRun(now).first); - mSyncQueue.remove(op5); - - assertEquals(op2, mSyncQueue.nextReadyToRun(now).first); - mSyncQueue.remove(op2); - - assertEquals(op3, mSyncQueue.nextReadyToRun(now).first); - mSyncQueue.remove(op3); - } - - Bundle newTestBundle(String val) { - Bundle bundle = new Bundle(); - bundle.putString("test", val); - return bundle; - } - - static class TestContext extends ContextWrapper { - ContentResolver mResolver; - - public TestContext(ContentResolver resolver, Context realContext) { - super(new RenamingDelegatingContext(new MockContext(), realContext, "test.")); - mResolver = resolver; - } - - @Override - public void enforceCallingOrSelfPermission(String permission, String message) { - } - - @Override - public ContentResolver getContentResolver() { - return mResolver; - } - } -} |