summaryrefslogtreecommitdiffstats
path: root/core/java/android/content/SyncManager.java
diff options
context:
space:
mode:
authorFred Quintana <fredq@google.com>2010-10-05 14:00:39 -0700
committerFred Quintana <fredq@google.com>2010-10-05 17:25:19 -0700
commit918339ab8255f8e1d03d8448ab1d9036c7c15173 (patch)
tree57673262b8cf27e993fe3413f5e73b1032da8cc5 /core/java/android/content/SyncManager.java
parente21635571eec6600abbfa2cd926383e788b12e12 (diff)
downloadframeworks_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.java932
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;
+ }
}