diff options
author | Fred Quintana <fredq@google.com> | 2010-10-05 14:00:39 -0700 |
---|---|---|
committer | Fred Quintana <fredq@google.com> | 2010-10-05 17:25:19 -0700 |
commit | 918339ab8255f8e1d03d8448ab1d9036c7c15173 (patch) | |
tree | 57673262b8cf27e993fe3413f5e73b1032da8cc5 /core/java/android/content/SyncManager.java | |
parent | e21635571eec6600abbfa2cd926383e788b12e12 (diff) | |
download | frameworks_base-918339ab8255f8e1d03d8448ab1d9036c7c15173.zip frameworks_base-918339ab8255f8e1d03d8448ab1d9036c7c15173.tar.gz frameworks_base-918339ab8255f8e1d03d8448ab1d9036c7c15173.tar.bz2 |
Make a separate active sync queue for initialization and regular syncs.
This entails allowing multiple syncs to happen in parallel, with
different limits for regular and initialization syncs.
Change-Id: I0e47c6515af5c98faf899f91855b342b0d0c708c
Diffstat (limited to 'core/java/android/content/SyncManager.java')
-rw-r--r-- | core/java/android/content/SyncManager.java | 932 |
1 files changed, 493 insertions, 439 deletions
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java index 950d339..c9115c5 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; + } } |