- changed periodic sync scheduling to just creating pending
  and changed the "get next operation to sync" logic just look
  at pending syncs, rather than them and periodic syncs
- made syncoperation dup-detection ignore the initialization
  sync extra
- made the sync dispatcher treat initialization syncs as just
  a regular sync request and also made it explicitly set or
  clear the initialization extra based on whether the sync
  adapter was in the syncable or unknown state
- change the getNextSync logic to prioritize syncable "unknown"
  syncs above everything else (since they should be fast and
  are important)
- make it reschedule completed initialization syncs if the
  sync adapter is now marked syncable
- fix some logging in SyncStorageEngine
- change SyncStorageEngine to not reuse authority ids when one
  is removed

http://b/issue?id=2531359
http://b/issue?id=2429638

Change-Id: I79805b582da74f4f0b6193eafaff24c2371d51e8
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 13da1ea..455815f 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -398,10 +398,6 @@
         return minValue + random.nextInt((int)spread);
     }
 
-    public ActiveSyncContext getActiveSyncContext() {
-        return mActiveSyncContext;
-    }
-
     public SyncStorageEngine getSyncStorageEngine() {
         return mSyncStorageEngine;
     }
@@ -412,11 +408,6 @@
         }
     }
 
-    public Account getSyncingAccount() {
-        ActiveSyncContext activeSyncContext = mActiveSyncContext;
-        return (activeSyncContext != null) ? activeSyncContext.mSyncOperation.account : null;
-    }
-
     private void initializeSyncAdapter(Account account, String authority) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "initializeSyncAdapter: " + account + ", authority " + authority);
@@ -425,7 +416,8 @@
         RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
                 mSyncAdapters.getServiceInfo(syncAdapterType);
         if (syncAdapterInfo == null) {
-            Log.w(TAG, "can't find a sync adapter for " + syncAdapterType);
+            Log.w(TAG, "can't find a sync adapter for " + syncAdapterType + ", removing");
+            mSyncStorageEngine.removeAuthority(account, authority);
             return;
         }
 
@@ -466,6 +458,8 @@
                 }
             } catch (RemoteException e) {
                 // doesn't matter, we will retry again later
+                Log.d(TAG, "error while initializing: " + mAccount + ", authority " + mAuthority,
+                        e);
             } finally {
                 // give the sync adapter time to initialize before unbinding from it
                 // TODO: change this API to not rely on this timing, http://b/2500805
@@ -602,16 +596,12 @@
                         continue;
                     }
 
-                    // initialize the SyncAdapter if the isSyncable state is unknown
-                    if (isSyncable < 0) {
-                        initializeSyncAdapter(account, authority);
-                        continue;
-                    }
-
-                    final boolean syncAutomatically = masterSyncAutomatically
-                            && mSyncStorageEngine.getSyncAutomatically(account, authority);
+                    // always allow if the isSyncable state is unknown
                     boolean syncAllowed =
-                            ignoreSettings || (backgroundDataUsageAllowed && syncAutomatically);
+                            (isSyncable < 0)
+                            || ignoreSettings
+                            || (backgroundDataUsageAllowed && masterSyncAutomatically
+                                && mSyncStorageEngine.getSyncAutomatically(account, authority));
                     if (!syncAllowed) {
                         if (isLoggable) {
                             Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority
@@ -1061,7 +1051,7 @@
                 pw.println(op.expedited);
                 if (op.extras != null && op.extras.size() > 0) {
                     sb.setLength(0);
-                    SyncOperation.extrasToStringBuilder(op.extras, sb);
+                    SyncOperation.extrasToStringBuilder(op.extras, sb, false /* asKey */);
                     pw.print("    extras: "); pw.println(sb.toString());
                 }
             }
@@ -1376,8 +1366,14 @@
         }
 
         public void handleMessage(Message msg) {
+            Long earliestFuturePollTime = null;
             try {
                 waitUntilReadyToRun();
+                // 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_SYNC_FINISHED:
                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -1486,48 +1482,90 @@
                 }
                 manageSyncNotification();
                 manageErrorNotification();
-                manageSyncAlarm();
+                manageSyncAlarm(earliestFuturePollTime);
                 mSyncTimeTracker.update();
             }
         }
 
-        private boolean isSyncAllowed(Account account, String authority, boolean ignoreSettings,
-                boolean backgroundDataUsageAllowed) {
-            Account[] accounts = mAccounts;
+        /**
+         * Turn any periodic sync operations that are ready to run into pending sync operations.
+         * @return the desired start time of the earliest future  periodic sync operation,
+         * in milliseconds since boot
+         */
+        private Long scheduleReadyPeriodicSyncs() {
+            final boolean backgroundDataUsageAllowed =
+                    getConnectivityManager().getBackgroundDataSetting();
+            Long earliestFuturePollTime = null;
+            if (!backgroundDataUsageAllowed || !mSyncStorageEngine.getMasterSyncAutomatically()) {
+                return earliestFuturePollTime;
+            }
+            final long nowAbsolute = System.currentTimeMillis();
+            ArrayList<SyncStorageEngine.AuthorityInfo> infos = mSyncStorageEngine.getAuthorities();
+            for (SyncStorageEngine.AuthorityInfo info : infos) {
+                // skip the sync if the account of this operation no longer exists
+                if (!ArrayUtils.contains(mAccounts, info.account)) {
+                    continue;
+                }
 
-            // skip the sync if the account of this operation no longer exists
-            if (!ArrayUtils.contains(accounts, account)) {
-                return false;
+                if (!mSyncStorageEngine.getSyncAutomatically(info.account, info.authority)) {
+                    continue;
+                }
+
+                if (mSyncStorageEngine.getIsSyncable(info.account, info.authority) == 0) {
+                    continue;
+                }
+
+                SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(info);
+                for (int i = 0, N = info.periodicSyncs.size(); i < N; i++) {
+                    final Bundle extras = info.periodicSyncs.get(i).first;
+                    final Long periodInSeconds = info.periodicSyncs.get(i).second;
+                    // find when this periodic sync was last scheduled to run
+                    final long lastPollTimeAbsolute = status.getPeriodicSyncTime(i);
+                    // compute when this periodic sync should next run
+                    long nextPollTimeAbsolute = lastPollTimeAbsolute + periodInSeconds * 1000;
+                    // if it is ready to run then schedule it and mark it as having been scheduled
+                    if (nextPollTimeAbsolute <= nowAbsolute) {
+                        scheduleSyncOperation(
+                                new SyncOperation(info.account, SyncStorageEngine.SOURCE_PERIODIC,
+                                        info.authority, extras, 0 /* delay */));
+                        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) {
+                            earliestFuturePollTime = nextPollTimeAbsolute;
+                        }
+                    }
+                }
             }
 
-            // skip the sync if it isn't manual and auto sync is disabled
-            final boolean syncAutomatically =
-                    mSyncStorageEngine.getSyncAutomatically(account, authority)
-                            && mSyncStorageEngine.getMasterSyncAutomatically();
-            if (!(ignoreSettings || (backgroundDataUsageAllowed && syncAutomatically))) {
-                return false;
+            if (earliestFuturePollTime == null) {
+                return null;
             }
 
-            if (mSyncStorageEngine.getIsSyncable(account, authority) <= 0) {
-                // if not syncable or if the syncable is unknown (< 0), don't allow
-                return false;
-            }
-
-            return true;
+            // convert absolute time to elapsed time
+            return SystemClock.elapsedRealtime()
+                    + ((earliestFuturePollTime < nowAbsolute)
+                      ? 0
+                      : (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) {
-                SyncOperation nextSyncOperation;
+                Pair<SyncOperation, Long> nextOpAndRunTime;
                 synchronized (mSyncQueue) {
-                    nextSyncOperation = getNextReadyToRunSyncOperation(now);
+                    nextOpAndRunTime = mSyncQueue.nextOperation();
                 }
-                if (nextSyncOperation != null) {
+                SyncOperation curOp = activeSyncContext.mSyncOperation;
+                if (nextOpAndRunTime != null
+                        && nextOpAndRunTime.second <= now
+                        && !nextOpAndRunTime.first.account.equals(curOp.account)
+                        && !nextOpAndRunTime.first.authority.equals(curOp.authority)) {
                     Log.d(TAG, "canceling and rescheduling sync because it ran too long: "
                             + activeSyncContext.mSyncOperation);
                     scheduleSyncOperation(new SyncOperation(activeSyncContext.mSyncOperation));
@@ -1574,27 +1612,48 @@
             // 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) {
-                    op = getNextReadyToRunSyncOperation(now);
-                    if (op == null) {
+                    Pair<SyncOperation, Long> nextOpAndRunTime = mSyncQueue.nextOperation();
+                    if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) {
                         if (isLoggable) {
-                            Log.v(TAG, "runStateIdle: no more sync operations, returning");
+                            Log.v(TAG, "runStateIdle: no more ready sync operations, returning");
                         }
                         return;
                     }
+                    op = nextOpAndRunTime.first;
 
-                    // we are either going to run this sync or drop it so go ahead and remove it
-                    // from the queue now
+                    // we are either going to run this sync or drop it so go ahead and
+                    // remove it from the queue now
                     mSyncQueue.remove(op);
 
-                    final boolean ignoreSettings = op.extras
-                            .getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
-                    if (!isSyncAllowed(op.account, op.authority, ignoreSettings,
-                            backgroundDataUsageAllowed)) {
+                    // drop the sync if the account of this operation no longer exists
+                    if (!ArrayUtils.contains(mAccounts, op.account)) {
+                        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);
+                    if (syncableState == 0) {
+                        continue;
+                    }
+
+                    // skip the sync if it isn't manual and auto sync or
+                    // background data usage is disabled
+                    if (!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false)
+                            && (syncableState > 0)
+                            && (!masterSyncAutomatically
+                                || !backgroundDataUsageAllowed
+                                || !mSyncStorageEngine.getSyncAutomatically(
+                                       op.account, op.authority))) {
                         continue;
                     }
 
@@ -1608,6 +1667,19 @@
                 }
             }
 
+            // 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);
+            }
+
             // connect to the sync adapter
             SyncAdapterType syncAdapterType = SyncAdapterType.newKey(op.authority, op.account.type);
             RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
@@ -1643,74 +1715,6 @@
             // MESSAGE_SERVICE_CONNECTED or MESSAGE_SERVICE_DISCONNECTED message
         }
 
-        private SyncOperation getNextPeriodicSyncOperation() {
-            final boolean backgroundDataUsageAllowed =
-                    getConnectivityManager().getBackgroundDataSetting();
-            SyncStorageEngine.AuthorityInfo best = null;
-            long bestPollTimeAbsolute = Long.MAX_VALUE;
-            Bundle bestExtras = null;
-            ArrayList<SyncStorageEngine.AuthorityInfo> infos = mSyncStorageEngine.getAuthorities();
-            for (SyncStorageEngine.AuthorityInfo info : infos) {
-                if (!isSyncAllowed(info.account, info.authority, false /* ignoreSettings */,
-                        backgroundDataUsageAllowed)) {
-                    continue;
-                }
-                SyncStatusInfo status = mSyncStorageEngine.getStatusByAccountAndAuthority(
-                        info.account, info.authority);
-                int i = 0;
-                for (Pair<Bundle, Long> periodicSync : info.periodicSyncs) {
-                    long lastPollTimeAbsolute = status != null ? status.getPeriodicSyncTime(i) : 0;
-                    final Bundle extras = periodicSync.first;
-                    final Long periodInSeconds = periodicSync.second;
-                    long nextPollTimeAbsolute = lastPollTimeAbsolute + periodInSeconds * 1000;
-                    if (nextPollTimeAbsolute < bestPollTimeAbsolute) {
-                        best = info;
-                        bestPollTimeAbsolute = nextPollTimeAbsolute;
-                        bestExtras = extras;
-                    }
-                    i++;
-                }
-            }
-
-            if (best == null) {
-                return null;
-            }
-
-            final long nowAbsolute = System.currentTimeMillis();
-            final SyncOperation syncOperation = new SyncOperation(best.account,
-                    SyncStorageEngine.SOURCE_PERIODIC,
-                    best.authority, bestExtras, 0 /* delay */);
-            syncOperation.earliestRunTime = SystemClock.elapsedRealtime()
-                    + (bestPollTimeAbsolute - nowAbsolute);
-            if (syncOperation.earliestRunTime < 0) {
-                syncOperation.earliestRunTime = 0;
-            }
-            return syncOperation;
-        }
-
-        public Pair<SyncOperation, Long> bestSyncOperationCandidate() {
-            Pair<SyncOperation, Long> nextOpAndRunTime = mSyncQueue.nextOperation();
-            SyncOperation nextOp = nextOpAndRunTime != null ? nextOpAndRunTime.first : null;
-            Long nextRunTime = nextOpAndRunTime != null ? nextOpAndRunTime.second : null;
-            SyncOperation pollOp = getNextPeriodicSyncOperation();
-            if (nextOp != null
-                    && (pollOp == null || nextOp.expedited
-                        || nextRunTime <= pollOp.earliestRunTime)) {
-                return nextOpAndRunTime;
-            } else if (pollOp != null) {
-                return Pair.create(pollOp, pollOp.earliestRunTime);
-            } else {
-                return null;
-            }
-        }
-
-        private SyncOperation getNextReadyToRunSyncOperation(long now) {
-            Pair<SyncOperation, Long> nextOpAndRunTime = bestSyncOperationCandidate();
-            return nextOpAndRunTime != null && nextOpAndRunTime.second <= now
-                    ? nextOpAndRunTime.first
-                    : null;
-        }
-
         private void runBoundToSyncAdapter(ISyncAdapter syncAdapter) {
             mActiveSyncContext.mSyncAdapter = syncAdapter;
             final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
@@ -1757,6 +1761,15 @@
                     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
@@ -1919,7 +1932,7 @@
             }
         }
 
-        private void manageSyncAlarm() {
+        private void manageSyncAlarm(Long earliestFuturePollElapsedTime) {
             // in each of these cases the sync loop will be kicked, which will cause this
             // method to be called again
             if (!mDataConnectionIsConnected) return;
@@ -1935,8 +1948,16 @@
             ActiveSyncContext activeSyncContext = mActiveSyncContext;
             if (activeSyncContext == null) {
                 synchronized (mSyncQueue) {
-                    Pair<SyncOperation, Long> candidate = bestSyncOperationCandidate();
-                    alarmTime = candidate != null ? candidate.second : null;
+                    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);
+                    }
                 }
             } else {
                 final long notificationTime =
@@ -2077,7 +2098,7 @@
                                 SyncStorageEngine.EVENT_STOP, syncOperation.syncSource,
                                 syncOperation.account.name.hashCode());
 
-            mSyncStorageEngine.stopSyncEvent(rowId, syncOperation.extras, elapsedTime,
+            mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime,
                     resultMessage, downstreamActivity, upstreamActivity);
         }
     }
diff --git a/core/java/android/content/SyncOperation.java b/core/java/android/content/SyncOperation.java
index ec601de..a7d036f 100644
--- a/core/java/android/content/SyncOperation.java
+++ b/core/java/android/content/SyncOperation.java
@@ -80,7 +80,7 @@
         sb.append("authority: ").append(authority);
         sb.append(" account: ").append(account);
         sb.append(" extras: ");
-        extrasToStringBuilder(extras, sb);
+        extrasToStringBuilder(extras, sb, false /* asKey */);
         sb.append(" syncSource: ").append(syncSource);
         sb.append(" when: ").append(earliestRunTime);
         sb.append(" expedited: ").append(expedited);
@@ -92,13 +92,19 @@
         sb.append("authority: ").append(authority);
         sb.append(" account: ").append(account);
         sb.append(" extras: ");
-        extrasToStringBuilder(extras, sb);
+        extrasToStringBuilder(extras, sb, true /* asKey */);
         return sb.toString();
     }
 
-    public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
+    public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb, boolean asKey) {
         sb.append("[");
         for (String key : bundle.keySet()) {
+            // if we are writing this as a key don't consider whether this
+            // is an initialization sync or not when computing the key since
+            // we set this flag appropriately when dispatching the sync request.
+            if (asKey && ContentResolver.SYNC_EXTRAS_INITIALIZE.equals(key)) {
+                continue;
+            }
             sb.append(key).append("=").append(bundle.get(key)).append(" ");
         }
         sb.append("]");
diff --git a/core/java/android/content/SyncQueue.java b/core/java/android/content/SyncQueue.java
index 432277f..28baa0da 100644
--- a/core/java/android/content/SyncQueue.java
+++ b/core/java/android/content/SyncQueue.java
@@ -16,6 +16,7 @@
 
 package android.content;
 
+import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
 
 import android.util.Pair;
@@ -120,14 +121,16 @@
 
     /**
      * Find the operation that should run next. Operations are sorted by their earliestRunTime,
-     * prioritizing expedited operations. The earliestRunTime is adjusted by the sync adapter's
-     * backoff and delayUntil times, if any.
+     * prioritizing first those with a syncable state of "unknown" that aren't retries then
+     * expedited operations.
+     * The earliestRunTime is adjusted by the sync adapter's backoff and delayUntil times, if any.
      * @return the operation that should run next and when it should run. The time may be in
      * the future. It is expressed in milliseconds since boot.
      */
     public Pair<SyncOperation, Long> nextOperation() {
         SyncOperation best = null;
         long bestRunTime = 0;
+        boolean bestSyncableIsUnknownAndNotARetry = false;
         for (SyncOperation op : mOperationsMap.values()) {
             long opRunTime = op.earliestRunTime;
             if (!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) {
@@ -137,12 +140,23 @@
                         Math.max(opRunTime, delayUntil),
                         backoff != null ? backoff.first : 0);
             }
-            // if the expedited state of both ops are the same then compare their runtime.
-            // Otherwise the candidate is only better than the current best if the candidate
-            // is expedited.
+            // we know a sync is a retry if the intialization flag is set, since that will only
+            // be set by the sync dispatching code, thus if it is set it must have already been
+            // dispatched
+            final boolean syncableIsUnknownAndNotARetry =
+                    !op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)
+                    && mSyncStorageEngine.getIsSyncable(op.account, op.authority) < 0;
+            // if the unsyncable state differs, make the current the best if it is unsyncable
+            // else, if the expedited state differs, make the current the best if it is expedited
+            // else, make the current the best if it is earlier than the best
             if (best == null
-                    || (best.expedited == op.expedited ? opRunTime < bestRunTime : op.expedited)) {
+                    || ((bestSyncableIsUnknownAndNotARetry == syncableIsUnknownAndNotARetry)
+                        ? (best.expedited == op.expedited
+                           ? opRunTime < bestRunTime
+                           : op.expedited)
+                        : syncableIsUnknownAndNotARetry)) {
                 best = op;
+                bestSyncableIsUnknownAndNotARetry = syncableIsUnknownAndNotARetry;
                 bestRunTime = opRunTime;
             }
         }
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index c848a00..984c070 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -59,7 +59,6 @@
  */
 public class SyncStorageEngine extends Handler {
     private static final String TAG = "SyncManager";
-    private static final boolean DEBUG = false;
     private static final boolean DEBUG_FILE = false;
 
     private static final long DEFAULT_POLL_FREQUENCY_SECONDS = 60 * 60 * 24; // One day
@@ -241,6 +240,8 @@
     private final RemoteCallbackList<ISyncStatusObserver> mChangeListeners
             = new RemoteCallbackList<ISyncStatusObserver>();
 
+    private int mNextAuthorityId = 0;
+
     // We keep 4 weeks of stats.
     private final DayStats[] mDayStats = new DayStats[7*4];
     private final Calendar mCal;
@@ -301,7 +302,11 @@
         readStatusLocked();
         readPendingOperationsLocked();
         readStatisticsLocked();
-        readLegacyAccountInfoLocked();
+        readAndDeleteLegacyAccountInfoLocked();
+        writeAccountInfoLocked();
+        writeStatusLocked();
+        writePendingOperationsLocked();
+        writeStatisticsLocked();
     }
 
     public static SyncStorageEngine newTestInstance(Context context) {
@@ -365,7 +370,9 @@
             mChangeListeners.finishBroadcast();
         }
 
-        if (DEBUG) Log.v(TAG, "reportChange " + which + " to: " + reports);
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "reportChange " + which + " to: " + reports);
+        }
 
         if (reports != null) {
             int i = reports.size();
@@ -402,15 +409,19 @@
     }
 
     public void setSyncAutomatically(Account account, String providerName, boolean sync) {
-        boolean wasEnabled;
+        Log.d(TAG, "setSyncAutomatically: " + account + ", provider " + providerName
+                + " -> " + sync);
         synchronized (mAuthorities) {
             AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false);
-            wasEnabled = authority.enabled;
+            if (authority.enabled == sync) {
+                Log.d(TAG, "setSyncAutomatically: already set to " + sync + ", doing nothing");
+                return;
+            }
             authority.enabled = sync;
             writeAccountInfoLocked();
         }
 
-        if (!wasEnabled && sync) {
+        if (sync) {
             ContentResolver.requestSync(account, providerName, new Bundle());
         }
         reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
@@ -440,7 +451,6 @@
     }
 
     public void setIsSyncable(Account account, String providerName, int syncable) {
-        int oldState;
         if (syncable > 1) {
             syncable = 1;
         } else if (syncable < -1) {
@@ -449,12 +459,15 @@
         Log.d(TAG, "setIsSyncable: " + account + ", provider " + providerName + " -> " + syncable);
         synchronized (mAuthorities) {
             AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false);
-            oldState = authority.syncable;
+            if (authority.syncable == syncable) {
+                Log.d(TAG, "setIsSyncable: already set to " + syncable + ", doing nothing");
+                return;
+            }
             authority.syncable = syncable;
             writeAccountInfoLocked();
         }
 
-        if (oldState <= 0 && syncable > 0) {
+        if (syncable > 0) {
             ContentResolver.requestSync(account, providerName, new Bundle());
         }
         reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
@@ -550,49 +563,60 @@
                     + " -> period " + period + ", extras " + extras);
         }
         synchronized (mAuthorities) {
-            AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false);
-            if (add) {
-                boolean alreadyPresent = false;
-                for (int i = 0, N = authority.periodicSyncs.size(); i < N; i++) {
-                    Pair<Bundle, Long> syncInfo = authority.periodicSyncs.get(i);
-                    final Bundle existingExtras = syncInfo.first;
-                    if (equals(existingExtras, extras)) {
-                        if (syncInfo.second == period) {
-                            return;
+            try {
+                AuthorityInfo authority =
+                        getOrCreateAuthorityLocked(account, providerName, -1, false);
+                if (add) {
+                    // add this periodic sync if one with the same extras doesn't already
+                    // exist in the periodicSyncs array
+                    boolean alreadyPresent = false;
+                    for (int i = 0, N = authority.periodicSyncs.size(); i < N; i++) {
+                        Pair<Bundle, Long> syncInfo = authority.periodicSyncs.get(i);
+                        final Bundle existingExtras = syncInfo.first;
+                        if (equals(existingExtras, extras)) {
+                            if (syncInfo.second == period) {
+                                return;
+                            }
+                            authority.periodicSyncs.set(i, Pair.create(extras, period));
+                            alreadyPresent = true;
+                            break;
                         }
-                        authority.periodicSyncs.set(i, Pair.create(extras, period));
-                        alreadyPresent = true;
-                        break;
+                    }
+                    // if we added an entry to the periodicSyncs array also add an entry to
+                    // the periodic syncs status to correspond to it
+                    if (!alreadyPresent) {
+                        authority.periodicSyncs.add(Pair.create(extras, period));
+                        SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
+                        status.setPeriodicSyncTime(authority.periodicSyncs.size() - 1, 0);
+                    }
+                } else {
+                    // remove any periodic syncs that match the authority and extras
+                    SyncStatusInfo status = mSyncStatus.get(authority.ident);
+                    boolean changed = false;
+                    Iterator<Pair<Bundle, Long>> iterator = authority.periodicSyncs.iterator();
+                    int i = 0;
+                    while (iterator.hasNext()) {
+                        Pair<Bundle, Long> syncInfo = iterator.next();
+                        if (equals(syncInfo.first, extras)) {
+                            iterator.remove();
+                            changed = true;
+                            // if we removed an entry from the periodicSyncs array also
+                            // remove the corresponding entry from the status
+                            if (status != null) {
+                                status.removePeriodicSyncTime(i);
+                            }
+                        } else {
+                            i++;
+                        }
+                    }
+                    if (!changed) {
+                        return;
                     }
                 }
-                if (!alreadyPresent) {
-                    authority.periodicSyncs.add(Pair.create(extras, period));
-                    SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
-                    status.setPeriodicSyncTime(authority.periodicSyncs.size() - 1, 0);
-                }
-            } else {
-                SyncStatusInfo status = mSyncStatus.get(authority.ident);
-                boolean changed = false;
-                Iterator<Pair<Bundle, Long>> iterator = authority.periodicSyncs.iterator();
-                int i = 0;
-                while (iterator.hasNext()) {
-                    Pair<Bundle, Long> syncInfo = iterator.next();
-                    if (equals(syncInfo.first, extras)) {
-                        iterator.remove();
-                        changed = true;
-                        if (status != null) {
-                            status.removePeriodicSyncTime(i);
-                        }
-                    } else {
-                        i++;
-                    }
-                }
-                if (!changed) {
-                    return;
-                }
+            } finally {
+                writeAccountInfoLocked();
+                writeStatusLocked();
             }
-            writeAccountInfoLocked();
-            writeStatusLocked();
         }
 
         reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
@@ -622,13 +646,14 @@
     }
 
     public void setMasterSyncAutomatically(boolean flag) {
-        boolean old;
         synchronized (mAuthorities) {
-            old = mMasterSyncAutomatically;
+            if (mMasterSyncAutomatically == flag) {
+                return;
+            }
             mMasterSyncAutomatically = flag;
             writeAccountInfoLocked();
         }
-        if (!old && flag) {
+        if (flag) {
             ContentResolver.requestSync(null, null, new Bundle());
         }
         reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
@@ -651,7 +676,7 @@
 
     public void removeAuthority(Account account, String authority) {
         synchronized (mAuthorities) {
-            removeAuthorityLocked(account, authority);
+            removeAuthorityLocked(account, authority, true /* doWrite */);
         }
     }
 
@@ -692,10 +717,12 @@
 
     public PendingOperation insertIntoPending(PendingOperation op) {
         synchronized (mAuthorities) {
-            if (DEBUG) Log.v(TAG, "insertIntoPending: account=" + op.account
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "insertIntoPending: account=" + op.account
                     + " auth=" + op.authority
                     + " src=" + op.syncSource
                     + " extras=" + op.extras);
+            }
 
             AuthorityInfo authority = getOrCreateAuthorityLocked(op.account,
                     op.authority,
@@ -721,10 +748,12 @@
     public boolean deleteFromPending(PendingOperation op) {
         boolean res = false;
         synchronized (mAuthorities) {
-            if (DEBUG) Log.v(TAG, "deleteFromPending: account=" + op.account
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "deleteFromPending: account=" + op.account
                     + " auth=" + op.authority
                     + " src=" + op.syncSource
                     + " extras=" + op.extras);
+            }
             if (mPendingOperations.remove(op)) {
                 if (mPendingOperations.size() == 0
                         || mNumPendingFinished >= PENDING_FINISH_TO_WRITE) {
@@ -737,7 +766,7 @@
                 AuthorityInfo authority = getAuthorityLocked(op.account, op.authority,
                         "deleteFromPending");
                 if (authority != null) {
-                    if (DEBUG) Log.v(TAG, "removing - " + authority);
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "removing - " + authority);
                     final int N = mPendingOperations.size();
                     boolean morePending = false;
                     for (int i=0; i<N; i++) {
@@ -750,7 +779,7 @@
                     }
 
                     if (!morePending) {
-                        if (DEBUG) Log.v(TAG, "no more pending!");
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "no more pending!");
                         SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
                         status.pending = false;
                     }
@@ -767,7 +796,9 @@
     public int clearPending() {
         int num;
         synchronized (mAuthorities) {
-            if (DEBUG) Log.v(TAG, "clearPending");
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "clearPending");
+            }
             num = mPendingOperations.size();
             mPendingOperations.clear();
             final int N = mSyncStatus.size();
@@ -806,14 +837,16 @@
      */
     public void doDatabaseCleanup(Account[] accounts) {
         synchronized (mAuthorities) {
-            if (DEBUG) Log.w(TAG, "Updating for new accounts...");
+            if (Log.isLoggable(TAG, Log.VERBOSE)) Log.w(TAG, "Updating for new accounts...");
             SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>();
             Iterator<AccountInfo> accIt = mAccounts.values().iterator();
             while (accIt.hasNext()) {
                 AccountInfo acc = accIt.next();
                 if (!ArrayUtils.contains(accounts, acc.account)) {
                     // This account no longer exists...
-                    if (DEBUG) Log.w(TAG, "Account removed: " + acc.account);
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.w(TAG, "Account removed: " + acc.account);
+                    }
                     for (AuthorityInfo auth : acc.authorities.values()) {
                         removing.put(auth.ident, auth);
                     }
@@ -859,11 +892,13 @@
     public void setActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
         synchronized (mAuthorities) {
             if (activeSyncContext != null) {
-                if (DEBUG) Log.v(TAG, "setActiveSync: account="
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "setActiveSync: account="
                         + activeSyncContext.mSyncOperation.account
                         + " auth=" + activeSyncContext.mSyncOperation.authority
                         + " src=" + activeSyncContext.mSyncOperation.syncSource
                         + " extras=" + activeSyncContext.mSyncOperation.extras);
+                }
                 if (mCurrentSync != null) {
                     Log.w(TAG, "setActiveSync called with existing active sync!");
                 }
@@ -878,7 +913,7 @@
                         authority.account, authority.authority,
                         activeSyncContext.mStartTime);
             } else {
-                if (DEBUG) Log.v(TAG, "setActiveSync: null");
+                if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "setActiveSync: null");
                 mCurrentSync = null;
             }
         }
@@ -900,8 +935,10 @@
             long now, int source) {
         long id;
         synchronized (mAuthorities) {
-            if (DEBUG) Log.v(TAG, "insertStartSyncEvent: account=" + accountName
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "insertStartSyncEvent: account=" + accountName
                     + " auth=" + authorityName + " source=" + source);
+            }
             AuthorityInfo authority = getAuthorityLocked(accountName, authorityName,
                     "insertStartSyncEvent");
             if (authority == null) {
@@ -919,7 +956,7 @@
                 mSyncHistory.remove(mSyncHistory.size()-1);
             }
             id = item.historyId;
-            if (DEBUG) Log.v(TAG, "returning historyId " + id);
+            if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "returning historyId " + id);
         }
 
         reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
@@ -944,10 +981,12 @@
         return true;
     }
 
-    public void stopSyncEvent(long historyId, Bundle extras, long elapsedTime, String resultMessage,
+    public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage,
             long downstreamActivity, long upstreamActivity) {
         synchronized (mAuthorities) {
-            if (DEBUG) Log.v(TAG, "stopSyncEvent: historyId=" + historyId);
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "stopSyncEvent: historyId=" + historyId);
+            }
             SyncHistoryItem item = null;
             int i = mSyncHistory.size();
             while (i > 0) {
@@ -989,14 +1028,6 @@
                     break;
                 case SOURCE_PERIODIC:
                     status.numSourcePeriodic++;
-                    AuthorityInfo authority = mAuthorities.get(item.authorityId);
-                    for (int periodicSyncIndex = 0;
-                            periodicSyncIndex < authority.periodicSyncs.size();
-                            periodicSyncIndex++) {
-                        if (equals(extras, authority.periodicSyncs.get(periodicSyncIndex).first)) {
-                            status.setPeriodicSyncTime(periodicSyncIndex, item.eventTime);
-                        }
-                    }
                     break;
             }
 
@@ -1261,18 +1292,14 @@
         AuthorityInfo authority = account.authorities.get(authorityName);
         if (authority == null) {
             if (ident < 0) {
-                // Look for a new identifier for this authority.
-                final int N = mAuthorities.size();
-                ident = 0;
-                for (int i=0; i<N; i++) {
-                    if (mAuthorities.valueAt(i).ident > ident) {
-                        break;
-                    }
-                    ident++;
-                }
+                ident = mNextAuthorityId;
+                mNextAuthorityId++;
+                doWrite = true;
             }
-            if (DEBUG) Log.v(TAG, "created a new AuthorityInfo for " + accountName
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "created a new AuthorityInfo for " + accountName
                     + ", provider " + authorityName);
+            }
             authority = new AuthorityInfo(accountName, authorityName, ident);
             account.authorities.put(authorityName, authority);
             mAuthorities.put(ident, authority);
@@ -1284,13 +1311,15 @@
         return authority;
     }
 
-    private void removeAuthorityLocked(Account account, String authorityName) {
+    private void removeAuthorityLocked(Account account, String authorityName, boolean doWrite) {
         AccountInfo accountInfo = mAccounts.get(account);
         if (accountInfo != null) {
             final AuthorityInfo authorityInfo = accountInfo.authorities.remove(authorityName);
             if (authorityInfo != null) {
                 mAuthorities.remove(authorityInfo.ident);
-                writeAccountInfoLocked();
+                if (doWrite) {
+                    writeAccountInfoLocked();
+                }
             }
         }
     }
@@ -1340,7 +1369,11 @@
             readStatusLocked();
             readPendingOperationsLocked();
             readStatisticsLocked();
-            readLegacyAccountInfoLocked();
+            readAndDeleteLegacyAccountInfoLocked();
+            writeAccountInfoLocked();
+            writeStatusLocked();
+            writePendingOperationsLocked();
+            writeStatisticsLocked();
         }
     }
 
@@ -1348,7 +1381,7 @@
      * Read all account information back in to the initial engine state.
      */
     private void readAccountInfoLocked() {
-        boolean writeNeeded = false;
+        int highestAuthorityId = -1;
         FileInputStream fis = null;
         try {
             fis = mAccountInfoFile.openRead();
@@ -1370,11 +1403,14 @@
                 } catch (NumberFormatException e) {
                     version = 0;
                 }
-                if (version < ACCOUNTS_VERSION) {
-                    writeNeeded = true;
+                String nextIdString = parser.getAttributeValue(null, "nextAuthorityId");
+                try {
+                    int id = (nextIdString == null) ? 0 : Integer.parseInt(nextIdString);
+                    mNextAuthorityId = Math.max(mNextAuthorityId, id);
+                } catch (NumberFormatException e) {
+                    // don't care
                 }
-                mMasterSyncAutomatically = listen == null
-                            || Boolean.parseBoolean(listen);
+                mMasterSyncAutomatically = listen == null || Boolean.parseBoolean(listen);
                 eventType = parser.next();
                 AuthorityInfo authority = null;
                 Pair<Bundle, Long> periodicSync = null;
@@ -1385,6 +1421,9 @@
                             if ("authority".equals(tagName)) {
                                 authority = parseAuthority(parser, version);
                                 periodicSync = null;
+                                if (authority.ident > highestAuthorityId) {
+                                    highestAuthorityId = authority.ident;
+                                }
                             }
                         } else if (parser.getDepth() == 3) {
                             if ("periodicSync".equals(tagName) && authority != null) {
@@ -1407,6 +1446,7 @@
             else Log.w(TAG, "Error reading accounts", e);
             return;
         } finally {
+            mNextAuthorityId = Math.max(highestAuthorityId + 1, mNextAuthorityId);
             if (fis != null) {
                 try {
                     fis.close();
@@ -1415,13 +1455,7 @@
             }
         }
 
-        if (maybeMigrateSettingsForRenamedAuthorities()) {
-            writeNeeded = true;
-        }
-
-        if (writeNeeded) {
-            writeAccountInfoLocked();
-        }
+        maybeMigrateSettingsForRenamedAuthorities();
     }
 
     /**
@@ -1463,7 +1497,8 @@
         }
 
         for (AuthorityInfo authorityInfo : authoritiesToRemove) {
-            removeAuthorityLocked(authorityInfo.account, authorityInfo.authority);
+            removeAuthorityLocked(authorityInfo.account, authorityInfo.authority,
+                    false /* doWrite */);
             writeNeeded = true;
         }
 
@@ -1593,6 +1628,7 @@
 
             out.startTag(null, "accounts");
             out.attribute(null, "version", Integer.toString(ACCOUNTS_VERSION));
+            out.attribute(null, "nextAuthorityId", Integer.toString(mNextAuthorityId));
             if (!mMasterSyncAutomatically) {
                 out.attribute(null, "listen-for-tickles", "false");
             }
@@ -1675,7 +1711,7 @@
      * erase it.  Note that we don't deal with pending operations, active
      * sync, or history.
      */
-    private void readLegacyAccountInfoLocked() {
+    private void readAndDeleteLegacyAccountInfoLocked() {
         // Look for old database to initialize from.
         File file = mContext.getDatabasePath("syncmanager.db");
         if (!file.exists()) {
@@ -1792,8 +1828,6 @@
 
             db.close();
 
-            writeAccountInfoLocked();
-            writeStatusLocked();
             (new File(path)).delete();
         }
     }
diff --git a/core/tests/coretests/src/android/content/SyncStorageEngineTest.java b/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
index 48fe765..f840512 100644
--- a/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
+++ b/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
@@ -51,7 +51,7 @@
         long historyId = engine.insertStartSyncEvent(
                 account, authority, time0, SyncStorageEngine.SOURCE_LOCAL);
         long time1 = time0 + SyncStorageEngine.MILLIS_IN_4WEEKS * 2;
-        engine.stopSyncEvent(historyId, new Bundle(), time1 - time0, "yay", 0, 0);
+        engine.stopSyncEvent(historyId, time1 - time0, "yay", 0, 0);
     }
 
     /**