- 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/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();
         }
     }