fix expedited syncs. there were times when an expedited sync wouldn't
correctly preempt non-expedited syncs

Change-Id: Ia88ce6504c06d7c8e50e40362e8bf2b85bd0934b
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 34c40a0..badcb03 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -17,7 +17,6 @@
 package android.content;
 
 import com.android.internal.R;
-import com.android.internal.util.ArrayUtils;
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
 
@@ -32,7 +31,6 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.app.DownloadManager.Request;
 import android.content.SyncStorageEngine.OnSyncRequestListener;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -1998,6 +1996,7 @@
                 ActiveSyncContext conflict = null;
                 ActiveSyncContext longRunning = null;
                 ActiveSyncContext toReschedule = null;
+                ActiveSyncContext oldestNonExpeditedRegular = null;
 
                 for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
                     final SyncOperation activeOp = activeSyncContext.mSyncOperation;
@@ -2005,6 +2004,13 @@
                         numInit++;
                     } else {
                         numRegular++;
+                        if (!activeOp.isExpedited()) {
+                            if (oldestNonExpeditedRegular == null
+                                || (oldestNonExpeditedRegular.mStartTime
+                                    > activeSyncContext.mStartTime)) {
+                                oldestNonExpeditedRegular = activeSyncContext;
+                            }
+                        }
                     }
                     if (activeOp.account.type.equals(candidate.account.type)
                             && activeOp.authority.equals(candidate.authority)
@@ -2027,8 +2033,13 @@
                     Log.v(TAG, "  numActiveInit=" + numInit + ", numActiveRegular=" + numRegular);
                     Log.v(TAG, "  longRunning: " + longRunning);
                     Log.v(TAG, "  conflict: " + conflict);
+                    Log.v(TAG, "  oldestNonExpeditedRegular: " + oldestNonExpeditedRegular);
                 }
 
+                final boolean roomAvailable = candidateIsInitialization
+                        ? numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS
+                        : numRegular < MAX_SIMULTANEOUS_REGULAR_SYNCS;
+
                 if (conflict != null) {
                     if (candidateIsInitialization && !conflict.mSyncOperation.isInitialization()
                             && numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS) {
@@ -2048,23 +2059,32 @@
                     } 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;
+                } else if (roomAvailable) {
+                    // dispatch candidate
+                } else if (candidate.isExpedited() && oldestNonExpeditedRegular != null
+                           && !candidateIsInitialization) {
+                    // We found an active, non-expedited regular sync. We also know that the
+                    // candidate doesn't conflict with this active sync since conflict
+                    // is null. Reschedule the active sync and start the candidate.
+                    toReschedule = oldestNonExpeditedRegular;
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "canceling and rescheduling sync since an expedited is ready to run, "
+                                + oldestNonExpeditedRegular);
                     }
+                } else if (longRunning != null
+                        && (candidateIsInitialization
+                            == longRunning.mSyncOperation.isInitialization())) {
+                    // We found an active, long-running sync. Reschedule the active
+                    // sync and start the candidate.
+                    toReschedule = longRunning;
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "canceling and rescheduling sync since it ran roo long, "
+                              + longRunning);
+                    }
+                } else {
+                    // we were unable to find or make space to run this candidate, go on to
+                    // the next one
+                    continue;
                 }
 
                 if (toReschedule != null) {
@@ -2516,7 +2536,7 @@
 
             return mSyncStorageEngine.insertStartSyncEvent(
                     syncOperation.account, syncOperation.userId, syncOperation.authority,
-                    now, source);
+                    now, source, syncOperation.isInitialization());
         }
 
         public void stopSyncEvent(long rowId, SyncOperation syncOperation, String resultMessage,
diff --git a/core/java/android/content/SyncOperation.java b/core/java/android/content/SyncOperation.java
index 4e86ef8..9fcc22d 100644
--- a/core/java/android/content/SyncOperation.java
+++ b/core/java/android/content/SyncOperation.java
@@ -116,6 +116,10 @@
         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
     }
 
+    public boolean isExpedited() {
+        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
+    }
+
     public boolean ignoreBackoff() {
         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
     }
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index d821918..6c7e940 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -221,6 +221,7 @@
         long upstreamActivity;
         long downstreamActivity;
         String mesg;
+        boolean initialization;
     }
 
     public static class DayStats {
@@ -1012,7 +1013,7 @@
      * Note that sync has started for the given account and authority.
      */
     public long insertStartSyncEvent(Account accountName, int userId, String authorityName,
-            long now, int source) {
+                                     long now, int source, boolean initialization) {
         long id;
         synchronized (mAuthorities) {
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -1025,6 +1026,7 @@
                 return -1;
             }
             SyncHistoryItem item = new SyncHistoryItem();
+            item.initialization = initialization;
             item.authorityId = authority.ident;
             item.historyId = mNextHistoryId++;
             if (mNextHistoryId < 0) mNextHistoryId = 0;
diff --git a/core/tests/coretests/src/android/content/SyncStorageEngineTest.java b/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
index 96f313a..2add623 100644
--- a/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
+++ b/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
@@ -20,7 +20,6 @@
 
 import android.accounts.Account;
 import android.os.Bundle;
-import android.os.Debug;
 import android.test.AndroidTestCase;
 import android.test.RenamingDelegatingContext;
 import android.test.mock.MockContentResolver;
@@ -57,7 +56,8 @@
 
         long time0 = 1000;
         long historyId = engine.insertStartSyncEvent(
-                account, 0, authority, time0, SyncStorageEngine.SOURCE_LOCAL);
+                account, 0, authority, time0, SyncStorageEngine.SOURCE_LOCAL,
+                false /* initialization */);
         long time1 = time0 + SyncStorageEngine.MILLIS_IN_4WEEKS * 2;
         engine.stopSyncEvent(historyId, time1 - time0, "yay", 0, 0);
     }