Fix broken javadocs

Change-Id: Ibf7f2ed92919efd36fffa963447b1a443c0bb9db
diff --git a/Android.mk b/Android.mk
index 1f8993c..9e06034 100644
--- a/Android.mk
+++ b/Android.mk
@@ -105,9 +105,9 @@
 	core/java/android/content/IIntentReceiver.aidl \
 	core/java/android/content/IIntentSender.aidl \
 	core/java/android/content/IOnPrimaryClipChangedListener.aidl \
-	core/java/android/content/IAnonymousSyncAdapter.aidl \
 	core/java/android/content/ISyncAdapter.aidl \
 	core/java/android/content/ISyncContext.aidl \
+	core/java/android/content/ISyncServiceAdapter.aidl \
 	core/java/android/content/ISyncStatusObserver.aidl \
 	core/java/android/content/pm/IPackageDataObserver.aidl \
 	core/java/android/content/pm/IPackageDeleteObserver.aidl \
diff --git a/api/current.txt b/api/current.txt
index c7a68ab..2e887e5 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5741,6 +5741,7 @@
     method public final android.os.Bundle call(android.net.Uri, java.lang.String, java.lang.String, android.os.Bundle);
     method public deprecated void cancelSync(android.net.Uri);
     method public static void cancelSync(android.accounts.Account, java.lang.String);
+    method public static void cancelSync(android.content.ComponentName);
     method public static void cancelSync(android.content.SyncRequest);
     method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
     method public static deprecated android.content.SyncInfo getCurrentSync();
@@ -5750,13 +5751,17 @@
     method public static boolean getMasterSyncAutomatically();
     method public android.net.Uri[] getOutgoingUriPermissionGrants(int, int);
     method public static java.util.List<android.content.PeriodicSync> getPeriodicSyncs(android.accounts.Account, java.lang.String);
+    method public static java.util.List<android.content.PeriodicSync> getPeriodicSyncs(android.content.ComponentName);
     method public java.lang.String[] getStreamTypes(android.net.Uri, java.lang.String);
     method public static android.content.SyncAdapterType[] getSyncAdapterTypes();
     method public static boolean getSyncAutomatically(android.accounts.Account, java.lang.String);
     method public final java.lang.String getType(android.net.Uri);
     method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
+    method public static boolean isServiceActive(android.content.ComponentName);
     method public static boolean isSyncActive(android.accounts.Account, java.lang.String);
+    method public static boolean isSyncActive(android.content.ComponentName);
     method public static boolean isSyncPending(android.accounts.Account, java.lang.String);
+    method public static boolean isSyncPending(android.content.ComponentName);
     method public void notifyChange(android.net.Uri, android.database.ContentObserver);
     method public void notifyChange(android.net.Uri, android.database.ContentObserver, boolean);
     method public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
@@ -5777,6 +5782,7 @@
     method public static void requestSync(android.content.SyncRequest);
     method public static void setIsSyncable(android.accounts.Account, java.lang.String, int);
     method public static void setMasterSyncAutomatically(boolean);
+    method public static void setServiceActive(android.content.ComponentName, boolean);
     method public static void setSyncAutomatically(android.accounts.Account, java.lang.String, boolean);
     method public deprecated void startSync(android.net.Uri, android.os.Bundle);
     method public final void unregisterContentObserver(android.database.ContentObserver);
@@ -6839,6 +6845,7 @@
     method public void writeToParcel(android.os.Parcel, int);
     field public final android.accounts.Account account;
     field public final java.lang.String authority;
+    field public final android.content.ComponentName service;
     field public final long startTime;
   }
 
@@ -6852,7 +6859,7 @@
   public static class SyncRequest.Builder {
     ctor public SyncRequest.Builder();
     method public android.content.SyncRequest build();
-    method public android.content.SyncRequest.Builder setAllowMetered(boolean);
+    method public android.content.SyncRequest.Builder setDisallowMetered(boolean);
     method public android.content.SyncRequest.Builder setExpedited(boolean);
     method public android.content.SyncRequest.Builder setExtras(android.os.Bundle);
     method public android.content.SyncRequest.Builder setIgnoreBackoff(boolean);
@@ -6894,6 +6901,7 @@
     ctor public SyncService();
     method public android.os.IBinder onBind(android.content.Intent);
     method public abstract void onPerformSync(android.os.Bundle, android.content.SyncResult);
+    method protected boolean parallelSyncsEnabled();
   }
 
   public class SyncStats implements android.os.Parcelable {
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index a761a89..1fcf311 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -141,7 +141,7 @@
     public static final String SYNC_EXTRAS_PRIORITY = "sync_priority";
 
     /** {@hide} Flag to allow sync to occur on metered network. */
-    public static final String SYNC_EXTRAS_ALLOW_METERED = "allow_metered";
+    public static final String SYNC_EXTRAS_DISALLOW_METERED = "allow_metered";
 
     /**
      * Set by the SyncManager to request that the SyncAdapter initialize itself for
@@ -1744,12 +1744,25 @@
      */
     public static void cancelSync(Account account, String authority) {
         try {
-            getContentService().cancelSync(account, authority);
+            getContentService().cancelSync(account, authority, null);
         } catch (RemoteException e) {
         }
     }
 
     /**
+     * Cancel any active or pending syncs that are running on this service.
+     *
+     * @param cname the service for which to cancel all active/pending operations.
+     */
+    public static void cancelSync(ComponentName cname) {
+        try {
+            getContentService().cancelSync(null, null, cname);
+        } catch (RemoteException e) {
+            
+        }
+    }
+
+    /**
      * Get information about the SyncAdapters that are known to the system.
      * @return an array of SyncAdapters that have registered with the system
      */
@@ -1815,6 +1828,10 @@
      *
      * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}.
+     * <p>The bundle for a periodic sync can be queried by applications with the correct
+     * permissions using
+     * {@link ContentResolver#getPeriodicSyncs(Account account, String provider)}, so no
+     * sensitive data should be transferred here.
      *
      * @param account the account to specify in the sync
      * @param authority the provider to specify in the sync request
@@ -1832,13 +1849,7 @@
         if (authority == null) {
             throw new IllegalArgumentException("authority must not be null");
         }
-        if (extras.getBoolean(SYNC_EXTRAS_MANUAL, false)
-                || extras.getBoolean(SYNC_EXTRAS_DO_NOT_RETRY, false)
-                || extras.getBoolean(SYNC_EXTRAS_IGNORE_BACKOFF, false)
-                || extras.getBoolean(SYNC_EXTRAS_IGNORE_SETTINGS, false)
-                || extras.getBoolean(SYNC_EXTRAS_INITIALIZE, false)
-                || extras.getBoolean(SYNC_EXTRAS_FORCE, false)
-                || extras.getBoolean(SYNC_EXTRAS_EXPEDITED, false)) {
+        if (invalidPeriodicExtras(extras)) {
             throw new IllegalArgumentException("illegal extras were set");
         }
         try {
@@ -1850,6 +1861,26 @@
     }
 
     /**
+     * {@hide}
+     * Helper function to throw an <code>IllegalArgumentException</code> if any illegal
+     * extras were set for a periodic sync.
+     *
+     * @param extras bundle to validate.
+     */
+    public static boolean invalidPeriodicExtras(Bundle extras) {
+        if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)
+                || extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)
+                || extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)
+                || extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false)
+                || extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)
+                || extras.getBoolean(ContentResolver.SYNC_EXTRAS_FORCE, false)
+                || extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * Remove a periodic sync. Has no affect if account, authority and extras don't match
      * an existing periodic sync.
      * <p>This method requires the caller to hold the permission
@@ -1875,19 +1906,28 @@
     }
 
     /**
-     * Remove the specified sync. This will remove any syncs that have been scheduled to run, but
-     * will not cancel any running syncs.
-     * <p>This method requires the caller to hold the permission</p>
-     * If the request is for a periodic sync this will cancel future occurrences of the sync.
-     *
-     * It is possible to cancel a sync using a SyncRequest object that is different from the object
-     * with which you requested the sync. Do so by building a SyncRequest with exactly the same
+     * Remove the specified sync. This will cancel any pending or active syncs. If the request is
+     * for a periodic sync, this call will remove any future occurrences.
+     * <p>If a periodic sync is specified, the caller must hold the permission
+     * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}. If this SyncRequest targets a
+     * SyncService adapter,the calling application must be signed with the same certificate as the
+     * adapter.
+     *</p>It is possible to cancel a sync using a SyncRequest object that is not the same object
+     * with which you requested the sync. Do so by building a SyncRequest with the same
      * service/adapter, frequency, <b>and</b> extras bundle.
      *
      * @param request SyncRequest object containing information about sync to cancel.
      */
     public static void cancelSync(SyncRequest request) {
-        // TODO: Finish this implementation.
+        if (request == null) {
+            throw new IllegalArgumentException("request cannot be null");
+        }
+        try {
+            getContentService().cancelRequest(request);
+        } catch (RemoteException e) {
+            // exception ignored; if this is thrown then it means the runtime is in the midst of
+            // being restarted
+        }
     }
 
     /**
@@ -1907,7 +1947,23 @@
             throw new IllegalArgumentException("authority must not be null");
         }
         try {
-            return getContentService().getPeriodicSyncs(account, authority);
+            return getContentService().getPeriodicSyncs(account, authority, null);
+        } catch (RemoteException e) {
+            throw new RuntimeException("the ContentService should always be reachable", e);
+        }
+    }
+
+    /**
+     * Return periodic syncs associated with the provided component.
+     * <p>The calling application must be signed with the same certificate as the target component,
+     * otherwise this call will fail.
+     */
+    public static List<PeriodicSync> getPeriodicSyncs(ComponentName cname) {
+        if (cname == null) {
+            throw new IllegalArgumentException("Component must not be null");
+        }
+        try {
+            return getContentService().getPeriodicSyncs(null, null, cname);
         } catch (RemoteException e) {
             throw new RuntimeException("the ContentService should always be reachable", e);
         }
@@ -1943,6 +1999,38 @@
     }
 
     /**
+     * Set whether the provided {@link SyncService} is available to process work.
+     * <p>This method requires the caller to hold the permission
+     * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}.
+     * <p>The calling application must be signed with the same certificate as the target component,
+     * otherwise this call will fail.
+     */
+    public static void setServiceActive(ComponentName cname, boolean active) {
+        try {
+            getContentService().setServiceActive(cname, active);
+        } catch (RemoteException e) {
+            // exception ignored; if this is thrown then it means the runtime is in the midst of
+            // being restarted
+        }
+    }
+
+    /**
+     * Query the state of this sync service.
+     * <p>Set with {@link #setServiceActive(ComponentName cname, boolean active)}.
+     * <p>The calling application must be signed with the same certificate as the target component,
+     * otherwise this call will fail.
+     * @param cname ComponentName referring to a {@link SyncService}
+     * @return true if jobs will be run on this service, false otherwise.
+     */
+    public static boolean isServiceActive(ComponentName cname) {
+        try {
+            return getContentService().isServiceActive(cname);
+        } catch (RemoteException e) {
+            throw new RuntimeException("the ContentService should always be reachable", e);
+        }
+    }
+
+    /**
      * Gets the master auto-sync setting that applies to all the providers and accounts.
      * If this is false then the per-provider auto-sync setting is ignored.
      * <p>This method requires the caller to hold the permission
@@ -1976,8 +2064,8 @@
     }
 
     /**
-     * Returns true if there is currently a sync operation for the given
-     * account or authority in the pending list, or actively being processed.
+     * Returns true if there is currently a sync operation for the given account or authority
+     * actively being processed.
      * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#READ_SYNC_STATS}.
      * @param account the account whose setting we are querying
@@ -1985,8 +2073,26 @@
      * @return true if a sync is active for the given account or authority.
      */
     public static boolean isSyncActive(Account account, String authority) {
+        if (account == null) {
+            throw new IllegalArgumentException("account must not be null");
+        }
+        if (authority == null) {
+            throw new IllegalArgumentException("authority must not be null");
+        }
+
         try {
-            return getContentService().isSyncActive(account, authority);
+            return getContentService().isSyncActive(account, authority, null);
+        } catch (RemoteException e) {
+            throw new RuntimeException("the ContentService should always be reachable", e);
+        }
+    }
+
+    public static boolean isSyncActive(ComponentName cname) {
+        if (cname == null) {
+            throw new IllegalArgumentException("component name must not be null");
+        }
+        try {
+            return getContentService().isSyncActive(null, null, cname);
         } catch (RemoteException e) {
             throw new RuntimeException("the ContentService should always be reachable", e);
         }
@@ -2044,7 +2150,7 @@
      */
     public static SyncStatusInfo getSyncStatus(Account account, String authority) {
         try {
-            return getContentService().getSyncStatus(account, authority);
+            return getContentService().getSyncStatus(account, authority, null);
         } catch (RemoteException e) {
             throw new RuntimeException("the ContentService should always be reachable", e);
         }
@@ -2060,7 +2166,15 @@
      */
     public static boolean isSyncPending(Account account, String authority) {
         try {
-            return getContentService().isSyncPending(account, authority);
+            return getContentService().isSyncPending(account, authority, null);
+        } catch (RemoteException e) {
+            throw new RuntimeException("the ContentService should always be reachable", e);
+        }
+    }
+
+    public static boolean isSyncPending(ComponentName cname) {
+        try {
+            return getContentService().isSyncPending(null, null, cname);
         } catch (RemoteException e) {
             throw new RuntimeException("the ContentService should always be reachable", e);
         }
diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl
index 9ad5a19..73a76e8 100644
--- a/core/java/android/content/IContentService.aidl
+++ b/core/java/android/content/IContentService.aidl
@@ -17,6 +17,7 @@
 package android.content;
 
 import android.accounts.Account;
+import android.content.ComponentName;
 import android.content.SyncInfo;
 import android.content.ISyncStatusObserver;
 import android.content.SyncAdapterType;
@@ -55,8 +56,14 @@
             int userHandle);
 
     void requestSync(in Account account, String authority, in Bundle extras);
+    /**
+     * Start a sync given a request.
+     */
     void sync(in SyncRequest request);
-    void cancelSync(in Account account, String authority);
+    void cancelSync(in Account account, String authority, in ComponentName cname);
+
+    /** Cancel a sync, providing information about the sync to be cancelled. */
+     void cancelRequest(in SyncRequest request);
 
     /**
      * Check if the provider should be synced when a network tickle is received
@@ -74,12 +81,14 @@
     void setSyncAutomatically(in Account account, String providerName, boolean sync);
 
     /**
-     * Get the frequency of the periodic poll, if any.
-     * @param providerName the provider whose setting we are querying
-     * @return the frequency of the periodic sync in seconds. If 0 then no periodic syncs
-     * will take place.
+     * Get a list of periodic operations for a specified authority, or service.
+     * @param account account for authority, must be null if cname is non-null.
+     * @param providerName name of provider, must be null if cname is non-null.
+     * @param cname component to identify sync service, must be null if account/providerName are
+     * non-null.
      */
-    List<PeriodicSync> getPeriodicSyncs(in Account account, String providerName);
+    List<PeriodicSync> getPeriodicSyncs(in Account account, String providerName,
+        in ComponentName cname);
 
     /**
      * Set whether or not the provider is to be synced on a periodic basis.
@@ -112,16 +121,23 @@
      */
     void setIsSyncable(in Account account, String providerName, int syncable);
 
+    /**
+     * Corresponds roughly to setIsSyncable(String account, String provider) for syncs that bind
+     * to a SyncService.
+     */
+    void setServiceActive(in ComponentName cname, boolean active);
+
+    /**
+     * Corresponds roughly to getIsSyncable(String account, String provider) for syncs that bind
+     * to a SyncService.
+     * @return 0 if this SyncService is not enabled, 1 if enabled, <0 if unknown.
+     */
+    boolean isServiceActive(in ComponentName cname);
+
     void setMasterSyncAutomatically(boolean flag);
 
     boolean getMasterSyncAutomatically();
 
-    /**
-     * Returns true if there is currently a sync operation for the given
-     * account or authority in the pending list, or actively being processed.
-     */
-    boolean isSyncActive(in Account account, String authority);
-
     List<SyncInfo> getCurrentSyncs();
 
     /**
@@ -131,17 +147,33 @@
     SyncAdapterType[] getSyncAdapterTypes();
 
     /**
+     * Returns true if there is currently a operation for the given account/authority or service
+     * actively being processed.
+     * @param account account for authority, must be null if cname is non-null.
+     * @param providerName name of provider, must be null if cname is non-null.
+     * @param cname component to identify sync service, must be null if account/providerName are
+     * non-null.
+     */
+    boolean isSyncActive(in Account account, String authority, in ComponentName cname);
+
+    /**
      * Returns the status that matches the authority. If there are multiples accounts for
      * the authority, the one with the latest "lastSuccessTime" status is returned.
-     * @param authority the authority whose row should be selected
-     * @return the SyncStatusInfo for the authority, or null if none exists
+     * @param account account for authority, must be null if cname is non-null.
+     * @param providerName name of provider, must be null if cname is non-null.
+     * @param cname component to identify sync service, must be null if account/providerName are
+     * non-null.
      */
-    SyncStatusInfo getSyncStatus(in Account account, String authority);
+    SyncStatusInfo getSyncStatus(in Account account, String authority, in ComponentName cname);
 
     /**
      * Return true if the pending status is true of any matching authorities.
+     * @param account account for authority, must be null if cname is non-null.
+     * @param providerName name of provider, must be null if cname is non-null.
+     * @param cname component to identify sync service, must be null if account/providerName are
+     * non-null.
      */
-    boolean isSyncPending(in Account account, String authority);
+    boolean isSyncPending(in Account account, String authority, in ComponentName cname);
 
     void addStatusChangeListener(int mask, ISyncStatusObserver callback);
 
diff --git a/core/java/android/content/IAnonymousSyncAdapter.aidl b/core/java/android/content/ISyncServiceAdapter.aidl
similarity index 97%
rename from core/java/android/content/IAnonymousSyncAdapter.aidl
rename to core/java/android/content/ISyncServiceAdapter.aidl
index a80cea3..d419307 100644
--- a/core/java/android/content/IAnonymousSyncAdapter.aidl
+++ b/core/java/android/content/ISyncServiceAdapter.aidl
@@ -24,7 +24,7 @@
  * Provider specified). See {@link android.content.AbstractThreadedSyncAdapter}.
  * {@hide}
  */
-oneway interface IAnonymousSyncAdapter {
+oneway interface ISyncServiceAdapter {
 
     /**
      * Initiate a sync. SyncAdapter-specific parameters may be specified in
diff --git a/core/java/android/content/PeriodicSync.java b/core/java/android/content/PeriodicSync.java
index 6aca151..836c6f8 100644
--- a/core/java/android/content/PeriodicSync.java
+++ b/core/java/android/content/PeriodicSync.java
@@ -35,7 +35,7 @@
     public final Bundle extras;
     /** How frequently the sync should be scheduled, in seconds. Kept around for API purposes. */
     public final long period;
-    /** Whether this periodic sync uses a service. */
+    /** Whether this periodic sync runs on a {@link SyncService}. */
     public final boolean isService;
     /**
      * How much flexibility can be taken in scheduling the sync, in seconds.
@@ -64,8 +64,6 @@
         this.flexTime = 0L;
     }
 
-    // TODO: Add copy ctor from SyncRequest?
-
     /**
      * Create a copy of a periodic sync.
      * {@hide}
@@ -183,7 +181,8 @@
     }
 
     /**
-     * Periodic sync extra comparison function.
+     * Periodic sync extra comparison function. Duplicated from
+     * {@link com.android.server.content.SyncManager#syncExtrasEquals(Bundle b1, Bundle b2)}
      * {@hide}
      */
     public static boolean syncExtrasEquals(Bundle b1, Bundle b2) {
diff --git a/core/java/android/content/SyncInfo.java b/core/java/android/content/SyncInfo.java
index 0284882..61b11c2 100644
--- a/core/java/android/content/SyncInfo.java
+++ b/core/java/android/content/SyncInfo.java
@@ -17,6 +17,7 @@
 package android.content;
 
 import android.accounts.Account;
+import android.content.pm.RegisteredServicesCache;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Parcelable.Creator;
@@ -29,16 +30,24 @@
     public final int authorityId;
 
     /**
-     * The {@link Account} that is currently being synced.
+     * The {@link Account} that is currently being synced. Will be null if this sync is running via
+     * a {@link SyncService}.
      */
     public final Account account;
 
     /**
-     * The authority of the provider that is currently being synced.
+     * The authority of the provider that is currently being synced. Will be null if this sync
+     * is running via a {@link SyncService}.
      */
     public final String authority;
 
     /**
+     * The {@link SyncService} that is targeted by this operation. Null if this sync is running via
+     * a {@link AbstractThreadedSyncAdapter}. 
+     */
+    public final ComponentName service;
+
+    /**
      * The start time of the current sync operation in milliseconds since boot.
      * This is represented in elapsed real time.
      * See {@link android.os.SystemClock#elapsedRealtime()}.
@@ -46,12 +55,13 @@
     public final long startTime;
 
     /** @hide */
-    public SyncInfo(int authorityId, Account account, String authority,
+    public SyncInfo(int authorityId, Account account, String authority, ComponentName service,
             long startTime) {
         this.authorityId = authorityId;
         this.account = account;
         this.authority = authority;
         this.startTime = startTime;
+        this.service = service;
     }
 
     /** @hide */
@@ -62,17 +72,20 @@
     /** @hide */
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeInt(authorityId);
-        account.writeToParcel(parcel, 0);
+        parcel.writeParcelable(account, flags);
         parcel.writeString(authority);
         parcel.writeLong(startTime);
+        parcel.writeParcelable(service, flags);
+        
     }
 
     /** @hide */
     SyncInfo(Parcel parcel) {
         authorityId = parcel.readInt();
-        account = new Account(parcel);
+        account = parcel.readParcelable(Account.class.getClassLoader());
         authority = parcel.readString();
         startTime = parcel.readLong();
+        service = parcel.readParcelable(ComponentName.class.getClassLoader());
     }
 
     /** @hide */
diff --git a/core/java/android/content/SyncRequest.java b/core/java/android/content/SyncRequest.java
index 4474c70..201a8b3 100644
--- a/core/java/android/content/SyncRequest.java
+++ b/core/java/android/content/SyncRequest.java
@@ -32,8 +32,8 @@
     private final ComponentName mComponentInfo;
     /** Bundle containing user info as well as sync settings. */
     private final Bundle mExtras;
-    /** Allow this sync request on metered networks. */
-    private final boolean mAllowMetered;
+    /** Don't allow this sync request on metered networks. */
+    private final boolean mDisallowMetered;
     /**
      * Anticipated upload size in bytes.
      * TODO: Not yet used - we put this information into the bundle for simplicity.
@@ -85,16 +85,32 @@
 
     /**
      * {@hide}
-     * Throws a runtime IllegalArgumentException if this function is called for an
-     * anonymous sync.
      *
-     * @return (Account, Provider) for this SyncRequest.
+     * @return account object for this sync.
+     * @throws IllegalArgumentException if this function is called for a request that targets a
+     * sync service.
      */
-    public Pair<Account, String> getProviderInfo() {
+    public Account getAccount() {
         if (!hasAuthority()) {
-            throw new IllegalArgumentException("Cannot getProviderInfo() for an anonymous sync.");
+            throw new IllegalArgumentException("Cannot getAccount() for a sync that targets a sync"
+                    + "service.");
         }
-        return Pair.create(mAccountToSync, mAuthority);
+        return mAccountToSync;
+    }
+
+    /**
+     * {@hide}
+     *
+     * @return provider for this sync.
+     * @throws IllegalArgumentException if this function is called for a request that targets a
+     * sync service.
+     */
+    public String getProvider() {
+        if (!hasAuthority()) {
+            throw new IllegalArgumentException("Cannot getProvider() for a sync that targets a"
+                    + "sync service.");
+        }
+        return mAuthority;
     }
 
     /**
@@ -159,7 +175,7 @@
         parcel.writeLong(mSyncFlexTimeSecs);
         parcel.writeLong(mSyncRunTimeSecs);
         parcel.writeInt((mIsPeriodic ? 1 : 0));
-        parcel.writeInt((mAllowMetered ? 1 : 0));
+        parcel.writeInt((mDisallowMetered ? 1 : 0));
         parcel.writeLong(mTxBytes);
         parcel.writeLong(mRxBytes);
         parcel.writeInt((mIsAuthority ? 1 : 0));
@@ -177,7 +193,7 @@
         mSyncFlexTimeSecs = in.readLong();
         mSyncRunTimeSecs = in.readLong();
         mIsPeriodic = (in.readInt() != 0);
-        mAllowMetered = (in.readInt() != 0);
+        mDisallowMetered = (in.readInt() != 0);
         mTxBytes = in.readLong();
         mRxBytes = in.readLong();
         mIsAuthority = (in.readInt() != 0);
@@ -207,7 +223,7 @@
         // For now we merge the sync config extras & the custom extras into one bundle.
         // TODO: pass the configuration extras through separately.
         mExtras.putAll(b.mSyncConfigExtras);
-        mAllowMetered = b.mAllowMetered;
+        mDisallowMetered = b.mDisallowMetered;
         mTxBytes = b.mTxBytes;
         mRxBytes = b.mRxBytes;
     }
@@ -253,7 +269,7 @@
         /** Expected download transfer in bytes. */
         private long mRxBytes = -1L;
         /** Whether or not this sync can occur on metered networks. Default false. */
-        private boolean mAllowMetered;
+        private boolean mDisallowMetered;
         /** Priority of this sync relative to others from calling app [-2, 2]. Default 0. */
         private int mPriority = 0;
         /**
@@ -344,6 +360,10 @@
          * You cannot reuse the same builder for one-time syncs after having specified a periodic
          * sync (by calling this function). If you do, an <code>IllegalArgumentException</code>
          * will be thrown.
+         * <p>The bundle for a periodic sync can be queried by applications with the correct
+         * permissions using
+         * {@link ContentResolver#getPeriodicSyncs(Account account, String provider)}, so no
+         * sensitive data should be transferred here.
          *
          * Example usage.
          *
@@ -407,10 +427,17 @@
         }
 
         /**
-         * @param allow false to allow this transfer on metered networks. Default true.
+         * Will throw an <code>IllegalArgumentException</code> if called and
+         * {@link #setIgnoreSettings(boolean ignoreSettings)} has already been called.
+         * @param disallow true to allow this transfer on metered networks. Default false.
+         * 
          */
-        public Builder setAllowMetered(boolean allow) {
-            mAllowMetered = true;
+        public Builder setDisallowMetered(boolean disallow) {
+            if (mIgnoreSettings && disallow) {
+                throw new IllegalArgumentException("setDisallowMetered(true) after having"
+                        + "specified that settings are ignored.");
+            }
+            mDisallowMetered = disallow;
             return this;
         }
 
@@ -511,10 +538,17 @@
          *
          * Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in
          * {@link #build()}.
+         * <p>Throws <code>IllegalArgumentException</code> if called and
+         * {@link #setDisallowMetered(boolean)} has been set.
+         * 
          *
          * @param ignoreSettings true to ignore the sync automatically settings. Default false.
          */
         public Builder setIgnoreSettings(boolean ignoreSettings) {
+            if (mDisallowMetered && ignoreSettings) {
+                throw new IllegalArgumentException("setIgnoreSettings(true) after having specified"
+                        + " sync settings with this builder.");
+            }
             mIgnoreSettings = ignoreSettings;
             return this;
         }
@@ -591,8 +625,8 @@
             if (mIgnoreBackoff) {
                 mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
             }
-            if (mAllowMetered) {
-                mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_ALLOW_METERED, true);
+            if (mDisallowMetered) {
+                mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, true);
             }
             if (mIgnoreSettings) {
                 mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
@@ -604,18 +638,27 @@
                 mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
             }
             if (mIsManual) {
-                mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
+                mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
+                mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
             }
             mSyncConfigExtras.putLong(ContentResolver.SYNC_EXTRAS_EXPECTED_UPLOAD, mTxBytes);
             mSyncConfigExtras.putLong(ContentResolver.SYNC_EXTRAS_EXPECTED_DOWNLOAD, mRxBytes);
             mSyncConfigExtras.putInt(ContentResolver.SYNC_EXTRAS_PRIORITY, mPriority);
             if (mSyncType == SYNC_TYPE_PERIODIC) {
                 // If this is a periodic sync ensure than invalid extras were not set.
-                validatePeriodicExtras(mCustomExtras);
-                validatePeriodicExtras(mSyncConfigExtras);
+                if (ContentResolver.invalidPeriodicExtras(mCustomExtras) || 
+                        ContentResolver.invalidPeriodicExtras(mSyncConfigExtras)) {
+                    throw new IllegalArgumentException("Illegal extras were set");
+                }
             } else if (mSyncType == SYNC_TYPE_UNKNOWN) {
                 throw new IllegalArgumentException("Must call either syncOnce() or syncPeriodic()");
             }
+            if (mSyncTarget == SYNC_TARGET_SERVICE) {
+                if (mSyncConfigExtras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) {
+                    throw new IllegalArgumentException("Cannot specify an initialisation sync"
+                            + " that targets a service.");
+                }
+            }
             // Ensure that a target for the sync has been set.
             if (mSyncTarget == SYNC_TARGET_UNKNOWN) {
                 throw new IllegalArgumentException("Must specify an adapter with one of"
@@ -623,23 +666,5 @@
             }
             return new SyncRequest(this);
         }
-
-        /**
-         * Helper function to throw an <code>IllegalArgumentException</code> if any illegal
-         * extras were set for a periodic sync.
-         *
-         * @param extras bundle to validate.
-         */
-        private void validatePeriodicExtras(Bundle extras) {
-            if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)
-                    || extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)
-                    || extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)
-                    || extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false)
-                    || extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)
-                    || extras.getBoolean(ContentResolver.SYNC_EXTRAS_FORCE, false)
-                    || extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) {
-                throw new IllegalArgumentException("Illegal extras were set");
-            }
-        }
-    }
+    }   
 }
diff --git a/core/java/android/content/SyncService.java b/core/java/android/content/SyncService.java
index 100fd40..3f99ce1 100644
--- a/core/java/android/content/SyncService.java
+++ b/core/java/android/content/SyncService.java
@@ -21,24 +21,28 @@
 import android.os.IBinder;
 import android.os.Process;
 import android.os.Trace;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 
-import java.util.HashMap;
-
 /**
  * Simplified @link android.content.AbstractThreadedSyncAdapter. Folds that
  * behaviour into a service to which the system can bind when requesting an
  * anonymous (providerless/accountless) sync.
  * <p>
- * In order to perform an anonymous sync operation you must extend this service,
- * implementing the abstract methods. This service must then be declared in the
- * application's manifest as usual. You can use this service for other work, however you
- * <b> must not </b> override the onBind() method unless you know what you're doing,
- * which limits the usefulness of this service for other work.
+ * In order to perform an anonymous sync operation you must extend this service, implementing the
+ * abstract methods. This service must be declared in the application's manifest as usual. You
+ * can use this service for other work, however you <b> must not </b> override the onBind() method
+ * unless you know what you're doing, which limits the usefulness of this service for other work.
+ * <p>A {@link SyncService} can either be active or inactive. Different to an
+ * {@link AbstractThreadedSyncAdapter}, there is no
+ * {@link ContentResolver#setSyncAutomatically(android.accounts.Account account, String provider, boolean sync)},
+ * as well as no concept of initialisation (you can handle your own if needed).
  *
  * <pre>
- * &lt;service ndroid:name=".MyAnonymousSyncService" android:permission="android.permission.SYNC" /&gt;
+ * &lt;service android:name=".MySyncService"/&gt;
  * </pre>
  * Like @link android.content.AbstractThreadedSyncAdapter this service supports
  * multiple syncs at the same time. Each incoming startSync() with a unique tag
@@ -48,41 +52,50 @@
  * at once, so if you mutate local objects you must ensure synchronization.
  */
 public abstract class SyncService extends Service {
+    private static final String TAG = "SyncService";
 
-    /** SyncAdapter Instantiation that any anonymous syncs call. */
-    private final AnonymousSyncAdapterImpl mSyncAdapter = new AnonymousSyncAdapterImpl();
+    private final SyncAdapterImpl mSyncAdapter = new SyncAdapterImpl();
 
-    /** Keep track of on-going syncs, keyed by tag. */
-    @GuardedBy("mLock")
-    private final HashMap<Bundle, AnonymousSyncThread>
-            mSyncThreads = new HashMap<Bundle, AnonymousSyncThread>();
+    /** Keep track of on-going syncs, keyed by bundle. */
+    @GuardedBy("mSyncThreadLock")
+    private final SparseArray<SyncThread>
+            mSyncThreads = new SparseArray<SyncThread>();
     /** Lock object for accessing the SyncThreads HashMap. */
     private final Object mSyncThreadLock = new Object();
+    /**
+     * Default key for if this sync service does not support parallel operations. Currently not
+     * sure if null keys will make it into the ArrayMap for KLP, so keeping our default for now.
+     */
+    private static final int KEY_DEFAULT = 0;
+    /** Identifier for this sync service. */
+    private ComponentName mServiceComponent;
 
-    @Override
+    /** {@hide} */
     public IBinder onBind(Intent intent) {
+        mServiceComponent = new ComponentName(this, getClass());
         return mSyncAdapter.asBinder();
     }
 
     /** {@hide} */
-    private class AnonymousSyncAdapterImpl extends IAnonymousSyncAdapter.Stub {
-
+    private class SyncAdapterImpl extends ISyncServiceAdapter.Stub {
         @Override
         public void startSync(ISyncContext syncContext, Bundle extras) {
             // Wrap the provided Sync Context because it may go away by the time
             // we call it.
             final SyncContext syncContextClient = new SyncContext(syncContext);
             boolean alreadyInProgress = false;
+            final int extrasAsKey = extrasToKey(extras);
             synchronized (mSyncThreadLock) {
-                if (mSyncThreads.containsKey(extras)) {
+                if (mSyncThreads.get(extrasAsKey) != null) {
+                    Log.e(TAG, "starting sync for : " + mServiceComponent);
+                    // Start sync.
+                    SyncThread syncThread = new SyncThread(syncContextClient, extras);
+                    mSyncThreads.put(extrasAsKey, syncThread);
+                    syncThread.start();
+                } else {
                     // Don't want to call back to SyncManager while still
                     // holding lock.
                     alreadyInProgress = true;
-                } else {
-                    AnonymousSyncThread syncThread = new AnonymousSyncThread(
-                            syncContextClient, extras);
-                    mSyncThreads.put(extras, syncThread);
-                    syncThread.start();
                 }
             }
             if (alreadyInProgress) {
@@ -91,14 +104,15 @@
         }
 
         /**
-         * Used by the SM to cancel a specific sync using the {@link
-         * com.android.server.content.SyncManager.ActiveSyncContext} as a handle.
+         * Used by the SM to cancel a specific sync using the
+         * com.android.server.content.SyncManager.ActiveSyncContext as a handle.
          */
         @Override
         public void cancelSync(ISyncContext syncContext) {
-            AnonymousSyncThread runningSync = null;
+            SyncThread runningSync = null;
             synchronized (mSyncThreadLock) {
-                for (AnonymousSyncThread thread : mSyncThreads.values()) {
+                for (int i = 0; i < mSyncThreads.size(); i++) {
+                    SyncThread thread = mSyncThreads.valueAt(i);
                     if (thread.mSyncContext.getSyncContextBinder() == syncContext.asBinder()) {
                         runningSync = thread;
                         break;
@@ -112,19 +126,39 @@
     }
 
     /**
+     * 
+     * @param extras Bundle for which to compute hash
+     * @return an integer hash that is equal to that of another bundle if they both contain the
+     * same key -> value mappings, however, not necessarily in order.
+     * Based on the toString() representation of the value mapped.
+     */
+    private int extrasToKey(Bundle extras) {
+        int hash = KEY_DEFAULT; // Empty bundle, or no parallel operations enabled.
+        if (parallelSyncsEnabled()) {
+            for (String key : extras.keySet()) {
+                String mapping = key + " " + extras.get(key).toString();
+                hash += mapping.hashCode();
+            }
+        }
+        return hash;
+    }
+
+    /**
      * {@hide}
      * Similar to {@link android.content.AbstractThreadedSyncAdapter.SyncThread}. However while
      * the ATSA considers an already in-progress sync to be if the account provided is currently
-     * syncing, this anonymous sync has no notion of account and therefore considers a sync unique
-     * if the provided bundle is different.
+     * syncing, this anonymous sync has no notion of account and considers a sync unique if the
+     * provided bundle is different.
      */
-    private class AnonymousSyncThread extends Thread {
+    private class SyncThread extends Thread {
         private final SyncContext mSyncContext;
         private final Bundle mExtras;
+        private final int mThreadsKey;
 
-        public AnonymousSyncThread(SyncContext syncContext, Bundle extras) {
+        public SyncThread(SyncContext syncContext, Bundle extras) {
             mSyncContext = syncContext;
             mExtras = extras;
+            mThreadsKey = extrasToKey(extras);
         }
 
         @Override
@@ -135,10 +169,8 @@
 
             SyncResult syncResult = new SyncResult();
             try {
-                if (isCancelled()) {
-                    return;
-                }
-                // Run the sync based off of the provided code.
+                if (isCancelled()) return;
+                // Run the sync.
                 SyncService.this.onPerformSync(mExtras, syncResult);
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_SYNC_MANAGER);
@@ -146,10 +178,9 @@
                     mSyncContext.onFinished(syncResult);
                 }
                 // Synchronize so that the assignment will be seen by other
-                // threads
-                // that also synchronize accesses to mSyncThreads.
+                // threads that also synchronize accesses to mSyncThreads.
                 synchronized (mSyncThreadLock) {
-                    mSyncThreads.remove(mExtras);
+                    mSyncThreads.remove(mThreadsKey);
                 }
             }
         }
@@ -166,4 +197,14 @@
      */
     public abstract void onPerformSync(Bundle extras, SyncResult syncResult);
 
+    /**
+     * Override this function to indicated whether you want to support parallel syncs.
+     * <p>If you override and return true multiple threads will be spawned within your Service to
+     * handle each concurrent sync request.
+     *
+     * @return false to indicate that this service does not support parallel operations by default.
+     */
+    protected boolean parallelSyncsEnabled() {
+        return false;
+    }
 }
diff --git a/services/java/com/android/server/content/ContentService.java b/services/java/com/android/server/content/ContentService.java
index a56af08..ab20461 100644
--- a/services/java/com/android/server/content/ContentService.java
+++ b/services/java/com/android/server/content/ContentService.java
@@ -25,6 +25,8 @@
 import android.content.IContentService;
 import android.content.ISyncStatusObserver;
 import android.content.PeriodicSync;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
 import android.content.SyncAdapterType;
 import android.content.SyncInfo;
 import android.content.SyncRequest;
@@ -315,7 +317,6 @@
         }
     }
 
-    @Override
     public void requestSync(Account account, String authority, Bundle extras) {
         ContentResolver.validateSyncExtrasBundle(extras);
         int userId = UserHandle.getCallingUserId();
@@ -345,61 +346,56 @@
      * Depending on the request, we enqueue to suit in the SyncManager.
      * @param request
      */
-    @Override
     public void sync(SyncRequest request) {
         Bundle extras = request.getBundle();
         ContentResolver.validateSyncExtrasBundle(extras);
 
-        long flextime = request.getSyncFlexTime();
-        long runAtTime = request.getSyncRunTime();
         int userId = UserHandle.getCallingUserId();
-        int uId = Binder.getCallingUid();
-
+        int callerUid = Binder.getCallingUid();
         // This makes it so that future permission checks will be in the context of this
         // process rather than the caller's process. We will restore this before returning.
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
-            if (syncManager != null) {
-                if (request.hasAuthority()) {
-                    // Sync Adapter registered with the system - old API.
-                    final  Account account = request.getProviderInfo().first;
-                    final String provider = request.getProviderInfo().second;
-                    if (request.isPeriodic()) {
-                        mContext.enforceCallingOrSelfPermission(
-                                Manifest.permission.WRITE_SYNC_SETTINGS,
-                                "no permission to write the sync settings");
-                        if (runAtTime < 60) {
-                            Slog.w(TAG, "Requested poll frequency of " + runAtTime
-                                    + " seconds being rounded up to 60 seconds.");
-                            runAtTime = 60;
-                        }
-                        PeriodicSync syncToAdd =
-                                new PeriodicSync(account, provider, extras, runAtTime, flextime);
-                        getSyncManager().getSyncStorageEngine().addPeriodicSync(syncToAdd, userId);
-                    } else {
-                        long beforeRuntimeMillis = (flextime) * 1000;
-                        long runtimeMillis = runAtTime * 1000;
-                        syncManager.scheduleSync(
-                                account, userId, uId, provider, extras,
-                                beforeRuntimeMillis, runtimeMillis,
-                                false /* onlyThoseWithUnknownSyncableState */);
-                    }
+            if (syncManager == null) return;
+
+            long flextime = request.getSyncFlexTime();
+            long runAtTime = request.getSyncRunTime();
+            if (request.isPeriodic()) {
+                mContext.enforceCallingOrSelfPermission(
+                        Manifest.permission.WRITE_SYNC_SETTINGS,
+                        "no permission to write the sync settings");
+                SyncStorageEngine.EndPoint info;
+                if (!request.hasAuthority()) {
+                    // Extra permissions checking for sync service.
+                    verifySignatureForPackage(callerUid,
+                            request.getService().getPackageName(), "sync");
+                    info = new SyncStorageEngine.EndPoint(request.getService(), userId);
                 } else {
-                    // Anonymous sync - new API.
-                    final ComponentName syncService = request.getService();
-                    if (request.isPeriodic()) {
-                        throw new RuntimeException("Periodic anonymous syncs not implemented yet.");
-                    } else {
-                        long beforeRuntimeMillis = (flextime) * 1000;
-                        long runtimeMillis = runAtTime * 1000;
-                        syncManager.scheduleSync(
-                              syncService, userId, uId, extras,
-                              beforeRuntimeMillis,
-                              runtimeMillis,
-                              false /* onlyThoseWithUnknownSyncableState */); // Empty function.
-                        throw new RuntimeException("One-off anonymous syncs not implemented yet.");
-                    }
+                    info = new SyncStorageEngine.EndPoint(
+                            request.getAccount(), request.getProvider(), userId);
+                }
+                if (runAtTime < 60) {
+                    Slog.w(TAG, "Requested poll frequency of " + runAtTime
+                            + " seconds being rounded up to 60 seconds.");
+                    runAtTime = 60;
+                }
+                // Schedule periodic sync.
+                getSyncManager().getSyncStorageEngine()
+                    .updateOrAddPeriodicSync(info, runAtTime, flextime, extras);
+            } else {
+                long beforeRuntimeMillis = (flextime) * 1000;
+                long runtimeMillis = runAtTime * 1000;
+                if (request.hasAuthority()) {
+                syncManager.scheduleSync(
+                        request.getAccount(), userId, callerUid, request.getProvider(), extras,
+                        beforeRuntimeMillis, runtimeMillis,
+                        false /* onlyThoseWithUnknownSyncableState */);
+                } else {
+                    syncManager.scheduleSync(
+                            request.getService(), userId, callerUid, extras,
+                            beforeRuntimeMillis,
+                            runtimeMillis); // Empty function.
                 }
             }
         } finally {
@@ -410,11 +406,13 @@
     /**
      * Clear all scheduled sync operations that match the uri and cancel the active sync
      * if they match the authority and account, if they are present.
-     * @param account filter the pending and active syncs to cancel using this account
-     * @param authority filter the pending and active syncs to cancel using this authority
+     *
+     * @param account filter the pending and active syncs to cancel using this account, or null.
+     * @param authority filter the pending and active syncs to cancel using this authority, or
+     * null.
+     * @param cname cancel syncs running on this service, or null for provider/account.
      */
-    @Override
-    public void cancelSync(Account account, String authority) {
+    public void cancelSync(Account account, String authority, ComponentName cname) {
         int userId = UserHandle.getCallingUserId();
 
         // This makes it so that future permission checks will be in the context of this
@@ -423,14 +421,54 @@
         try {
             SyncManager syncManager = getSyncManager();
             if (syncManager != null) {
-                syncManager.clearScheduledSyncOperations(account, userId, authority);
-                syncManager.cancelActiveSync(account, userId, authority);
+                SyncStorageEngine.EndPoint info;
+                if (cname == null) {
+                    info = new SyncStorageEngine.EndPoint(account, authority, userId);
+                } else {
+                    info = new SyncStorageEngine.EndPoint(cname, userId);
+                }
+                syncManager.clearScheduledSyncOperations(info);
+                syncManager.cancelActiveSync(info, null /* all syncs for this adapter */);
             }
         } finally {
             restoreCallingIdentity(identityToken);
         }
     }
 
+    public void cancelRequest(SyncRequest request) {
+        SyncManager syncManager = getSyncManager();
+        if (syncManager == null) return;
+        int userId = UserHandle.getCallingUserId();
+        int callerUid = Binder.getCallingUid();
+
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncStorageEngine.EndPoint info;
+            Bundle extras = new Bundle(request.getBundle());
+            if (request.hasAuthority()) {
+                Account account = request.getAccount();
+                String provider = request.getProvider();
+                info = new SyncStorageEngine.EndPoint(account, provider, userId);
+            } else {
+                // Only allowed to manipulate syncs for a service which you own.
+                ComponentName service = request.getService();
+                verifySignatureForPackage(callerUid, service.getPackageName(), "cancel");
+                info = new SyncStorageEngine.EndPoint(service, userId);
+            }
+            if (request.isPeriodic()) {
+                // Remove periodic sync.
+                mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+                        "no permission to write the sync settings");
+                getSyncManager().getSyncStorageEngine().removePeriodicSync(info, extras);
+            }
+            // Cancel active syncs and clear pending syncs from the queue.
+            syncManager.cancelScheduledSyncOperation(info, extras);
+            syncManager.cancelActiveSync(info, extras);
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
     /**
      * Get information about the SyncAdapters that are known to the system.
      * @return an array of SyncAdapters that have registered with the system
@@ -459,8 +497,8 @@
         try {
             SyncManager syncManager = getSyncManager();
             if (syncManager != null) {
-                return syncManager.getSyncStorageEngine().getSyncAutomatically(
-                        account, userId, providerName);
+                return syncManager.getSyncStorageEngine()
+                        .getSyncAutomatically(account, userId, providerName);
             }
         } finally {
             restoreCallingIdentity(identityToken);
@@ -478,18 +516,15 @@
         try {
             SyncManager syncManager = getSyncManager();
             if (syncManager != null) {
-                syncManager.getSyncStorageEngine().setSyncAutomatically(
-                        account, userId, providerName, sync);
+                syncManager.getSyncStorageEngine()
+                .setSyncAutomatically(account, userId, providerName, sync);
             }
         } finally {
             restoreCallingIdentity(identityToken);
         }
     }
 
-    /**
-     * Old API. Schedule periodic sync with default flex time.
-     */
-    @Override
+    /** Old API. Schedule periodic sync with default flex time. */
     public void addPeriodicSync(Account account, String authority, Bundle extras,
             long pollFrequency) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
@@ -501,21 +536,22 @@
                     + " seconds being rounded up to 60 seconds.");
             pollFrequency = 60;
         }
+        long defaultFlex = SyncStorageEngine.calculateDefaultFlexTime(pollFrequency);
 
         long identityToken = clearCallingIdentity();
         try {
-            // Add default flex time to this sync.
-            PeriodicSync syncToAdd =
-                    new PeriodicSync(account, authority, extras,
-                            pollFrequency,
-                            SyncStorageEngine.calculateDefaultFlexTime(pollFrequency));
-            getSyncManager().getSyncStorageEngine().addPeriodicSync(syncToAdd, userId);
+            SyncStorageEngine.EndPoint info =
+                    new SyncStorageEngine.EndPoint(account, authority, userId);
+            getSyncManager().getSyncStorageEngine()
+                .updateOrAddPeriodicSync(info,
+                        pollFrequency,
+                        defaultFlex,
+                        extras);
         } finally {
             restoreCallingIdentity(identityToken);
         }
     }
 
-    @Override
     public void removePeriodicSync(Account account, String authority, Bundle extras) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                 "no permission to write the sync settings");
@@ -523,32 +559,34 @@
 
         long identityToken = clearCallingIdentity();
         try {
-            PeriodicSync syncToRemove = new PeriodicSync(account, authority, extras,
-                    0 /* Not read for removal */, 0 /* Not read for removal */);
-            getSyncManager().getSyncStorageEngine().removePeriodicSync(syncToRemove, userId);
+            getSyncManager().getSyncStorageEngine()
+                .removePeriodicSync(
+                        new SyncStorageEngine.EndPoint(account, authority, userId),
+                        extras);
         } finally {
             restoreCallingIdentity(identityToken);
         }
     }
 
-    /**
-     * TODO: Implement.
-     * @param request Sync to remove.
-     */
-    public void removeSync(SyncRequest request) {
-
-    }
-
-    @Override
-    public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) {
+    public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName,
+            ComponentName cname) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
                 "no permission to read the sync settings");
         int userId = UserHandle.getCallingUserId();
+        int callerUid = Binder.getCallingUid();
 
         long identityToken = clearCallingIdentity();
         try {
-            return getSyncManager().getSyncStorageEngine().getPeriodicSyncs(
-                    account, userId, providerName);
+            if (cname == null) {
+                return getSyncManager().getSyncStorageEngine().getPeriodicSyncs(
+                        new SyncStorageEngine.EndPoint(account, providerName, userId));
+            } else if (account == null && providerName == null) {
+                verifySignatureForPackage(callerUid, cname.getPackageName(), "getPeriodicSyncs");
+                return getSyncManager().getSyncStorageEngine().getPeriodicSyncs(
+                        new SyncStorageEngine.EndPoint(cname, userId));
+            } else {
+                throw new IllegalArgumentException("Invalid authority specified");
+            }
         } finally {
             restoreCallingIdentity(identityToken);
         }
@@ -572,7 +610,6 @@
         return -1;
     }
 
-    @Override
     public void setIsSyncable(Account account, String providerName, int syncable) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                 "no permission to write the sync settings");
@@ -590,6 +627,43 @@
         }
     }
 
+    public void setServiceActive(ComponentName cname, boolean active) {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+                "no permission to write the sync settings");
+        verifySignatureForPackage(Binder.getCallingUid(), cname.getPackageName(), "setIsEnabled");
+
+        int userId = UserHandle.getCallingUserId();
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                int syncable = active ? 1 : 0;
+                syncManager.getSyncStorageEngine().setIsEnabled(
+                        cname, userId, syncable);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    public boolean isServiceActive(ComponentName cname) {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
+                "no permission to read the sync settings");
+        verifySignatureForPackage(Binder.getCallingUid(), cname.getPackageName(), "getIsEnabled");
+
+        int userId = UserHandle.getCallingUserId();
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                return (syncManager.getIsTargetServiceActive(cname, userId) == 1);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+        return false;
+    }
+
     @Override
     public boolean getMasterSyncAutomatically() {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
@@ -625,17 +699,24 @@
         }
     }
 
-    public boolean isSyncActive(Account account, String authority) {
+    public boolean isSyncActive(Account account, String authority, ComponentName cname) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
                 "no permission to read the sync stats");
         int userId = UserHandle.getCallingUserId();
-
+        int callingUid = Binder.getCallingUid();
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
-            if (syncManager != null) {
+            if (syncManager == null) {
+                return false;
+            }
+            if (cname == null) {
                 return syncManager.getSyncStorageEngine().isSyncActive(
-                        account, userId, authority);
+                        new SyncStorageEngine.EndPoint(account, authority, userId));
+            } else if (account == null && authority == null) {
+                verifySignatureForPackage(callingUid, cname.getPackageName(), "isSyncActive");
+                return syncManager.getSyncStorageEngine().isSyncActive(
+                        new SyncStorageEngine.EndPoint(cname, userId));
             }
         } finally {
             restoreCallingIdentity(identityToken);
@@ -656,39 +737,55 @@
         }
     }
 
-    public SyncStatusInfo getSyncStatus(Account account, String authority) {
+    public SyncStatusInfo getSyncStatus(Account account, String authority, ComponentName cname) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
                 "no permission to read the sync stats");
         int userId = UserHandle.getCallingUserId();
-
+        int callerUid = Binder.getCallingUid();
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
-            if (syncManager != null) {
-                return syncManager.getSyncStorageEngine().getStatusByAccountAndAuthority(
-                        account, userId, authority);
+            if (syncManager == null) {
+                return null;
             }
+            SyncStorageEngine.EndPoint info;
+            if (cname == null) {
+                info = new SyncStorageEngine.EndPoint(account, authority, userId);
+            } else if (account == null && authority == null) {
+                verifySignatureForPackage(callerUid, cname.getPackageName(), "getSyncStatus");
+                info = new SyncStorageEngine.EndPoint(cname, userId);
+            } else {
+                throw new IllegalArgumentException("Must call sync status with valid authority");
+            }
+            return syncManager.getSyncStorageEngine().getStatusByAuthority(info);
         } finally {
             restoreCallingIdentity(identityToken);
         }
-        return null;
     }
 
-    public boolean isSyncPending(Account account, String authority) {
+    public boolean isSyncPending(Account account, String authority, ComponentName cname) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
                 "no permission to read the sync stats");
         int userId = UserHandle.getCallingUserId();
-
+        int callerUid = Binder.getCallingUid();
         long identityToken = clearCallingIdentity();
+        SyncManager syncManager = getSyncManager();
+        if (syncManager == null) return false;
+
         try {
-            SyncManager syncManager = getSyncManager();
-            if (syncManager != null) {
-                return syncManager.getSyncStorageEngine().isSyncPending(account, userId, authority);
+            SyncStorageEngine.EndPoint info;
+            if (cname == null) {
+                info = new SyncStorageEngine.EndPoint(account, authority, userId);
+            } else if (account == null && authority == null) {
+                verifySignatureForPackage(callerUid, cname.getPackageName(), "isSyncPending");
+                info = new SyncStorageEngine.EndPoint(cname, userId);
+            } else {
+                throw new IllegalArgumentException("Invalid authority specified");
             }
+            return syncManager.getSyncStorageEngine().isSyncPending(info);
         } finally {
             restoreCallingIdentity(identityToken);
         }
-        return false;
     }
 
     public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
@@ -722,6 +819,30 @@
     }
 
     /**
+     * Helper to verify that the provided package name shares the same cert as the caller.
+     * @param callerUid uid of the calling process.
+     * @param packageName package to verify against package of calling application.
+     * @param tag a tag to use when throwing an exception if the signatures don't
+     * match. Cannot be null.
+     * @return true if the calling application and the provided package are signed with the same
+     * certificate.
+     */
+    private boolean verifySignatureForPackage(int callerUid, String packageName, String tag) {
+        PackageManager pm = mContext.getPackageManager();
+        try {
+            int serviceUid = pm.getApplicationInfo(packageName, 0).uid;
+            if (pm.checkSignatures(callerUid, serviceUid) == PackageManager.SIGNATURE_MATCH) {
+                return true;
+            } else {
+                throw new SecurityException(tag + ": Caller certificate does not match that for - "
+                        + packageName);
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new IllegalArgumentException(tag + ": " + packageName + " package not found.");
+        }
+    }
+
+    /**
      * Hide this class since it is not part of api,
      * but current unittest framework requires it to be public
      * @hide
diff --git a/services/java/com/android/server/content/SyncManager.java b/services/java/com/android/server/content/SyncManager.java
index a6b69a2..3802415 100644
--- a/services/java/com/android/server/content/SyncManager.java
+++ b/services/java/com/android/server/content/SyncManager.java
@@ -31,6 +31,7 @@
 import android.content.Context;
 import android.content.ISyncAdapter;
 import android.content.ISyncContext;
+import android.content.ISyncServiceAdapter;
 import android.content.ISyncStatusObserver;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -200,8 +201,9 @@
                             Log.v(TAG, "Internal storage is low.");
                         }
                         mStorageIsLow = true;
-                        cancelActiveSync(null /* any account */, UserHandle.USER_ALL,
-                                null /* any authority */);
+                        cancelActiveSync(
+                                SyncStorageEngine.EndPoint.USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL,
+                                null /* any sync */);
                     } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
                             Log.v(TAG, "Internal storage is ok.");
@@ -219,19 +221,6 @@
         }
     };
 
-    private BroadcastReceiver mBackgroundDataSettingChanged = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (getConnectivityManager().getBackgroundDataSetting()) {
-                scheduleSync(null /* account */, UserHandle.USER_ALL,
-                        SyncOperation.REASON_BACKGROUND_DATA_SETTINGS_CHANGED,
-                        null /* authority */,
-                        new Bundle(), 0 /* delay */, 0 /* delay */,
-                        false /* onlyThoseWithUnknownSyncableState */);
-            }
-        }
-    };
-
     private BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -276,16 +265,16 @@
             doDatabaseCleanup();
         }
 
+        AccountAndUser[] accounts = mRunningAccounts;
         for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
-            if (!containsAccountAndUser(mRunningAccounts,
-                    currentSyncContext.mSyncOperation.account,
-                    currentSyncContext.mSyncOperation.userId)) {
+            if (!containsAccountAndUser(accounts,
+                    currentSyncContext.mSyncOperation.target.account,
+                    currentSyncContext.mSyncOperation.target.userId)) {
                 Log.d(TAG, "canceling sync since the account is no longer running");
                 sendSyncFinishedOrCanceledMessage(currentSyncContext,
                         null /* no result since this is a cancel */);
             }
         }
-
         // we must do this since we don't bother scheduling alarms when
         // the accounts are not set yet
         sendCheckAlarmsMessage();
@@ -380,12 +369,17 @@
         mSyncStorageEngine = SyncStorageEngine.getSingleton();
         mSyncStorageEngine.setOnSyncRequestListener(new OnSyncRequestListener() {
             @Override
-            public void onSyncRequest(Account account, int userId, int reason, String authority,
-                    Bundle extras) {
-                scheduleSync(account, userId, reason, authority, extras,
-                    0 /* no delay */,
-                    0 /* no delay */,
-                    false);
+            public void onSyncRequest(SyncStorageEngine.EndPoint info, int reason, Bundle extras) {
+                if (info.target_provider) {
+                    scheduleSync(info.account, info.userId, reason, info.provider, extras,
+                        0 /* no flex */,
+                        0 /* run immediately */,
+                        false);
+                } else if (info.target_service) {
+                    scheduleSync(info.service, info.userId, reason, extras,
+                            0 /* no flex */,
+                            0 /* run immediately */);
+                }
             }
         });
 
@@ -417,9 +411,6 @@
             context.registerReceiver(mBootCompletedReceiver, intentFilter);
         }
 
-        intentFilter = new IntentFilter(ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
-        context.registerReceiver(mBackgroundDataSettingChanged, intentFilter);
-
         intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
         intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
         context.registerReceiver(mStorageIntentReceiver, intentFilter);
@@ -532,181 +523,106 @@
         }
     }
 
+    /**
+     * TODO: Decide if restricted users have different sync options for the sync service (as is
+     * the case with sync adapters).
+     */
+    public int getIsTargetServiceActive(ComponentName cname, int userId) {
+        return mSyncStorageEngine.getIsTargetServiceActive(cname, userId);
+    }
+
     private void ensureAlarmService() {
         if (mAlarmService == null) {
-            mAlarmService = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+            mAlarmService = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
         }
     }
 
     /**
      * Initiate a sync using the new anonymous service API.
-     * TODO: Implement.
      * @param cname SyncService component bound to in order to perform the sync. 
      * @param userId the id of the user whose accounts are to be synced. If userId is USER_ALL,
      *          then all users' accounts are considered.
      * @param uid Linux uid of the application that is performing the sync. 
      * @param extras a Map of SyncAdapter-specific information to control
      *          syncs of a specific provider. Can be null.
-     * @param beforeRunTimeMillis
-     *  @param runtimeMillis
+     * @param beforeRunTimeMillis milliseconds before <code>runtimeMillis</code> that this sync may
+     * be run.
+     * @param runtimeMillis milliseconds from now by which this sync must be run.
      */
     public void scheduleSync(ComponentName cname, int userId, int uid, Bundle extras,
-            long beforeRunTimeMillis, long runtimeMillis,
-            boolean onlyThoseWithUnknownSyncableState) {
-/**
+            long beforeRunTimeMillis, long runtimeMillis) {
         boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
-
-        final boolean backgroundDataUsageAllowed = !mBootCompleted ||
-                getConnectivityManager().getBackgroundDataSetting();
-
-        if (extras == null) {
-            extras = new Bundle();
-        }
         if (isLoggable) {
-            Log.e(TAG, requestedAccount + " " + extras.toString() + " " + requestedAuthority);
+            Log.d(TAG, "one off sync for: " + cname + " " + extras.toString());
         }
+
         Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
         if (expedited) {
             runtimeMillis = -1; // this means schedule at the front of the queue
         }
 
-        AccountAndUser[] accounts;
-        if (requestedAccount != null && userId != UserHandle.USER_ALL) {
-            accounts = new AccountAndUser[] { new AccountAndUser(requestedAccount, userId) };
-        } else {
-            // if the accounts aren't configured yet then we can't support an account-less
-            // sync request
-            accounts = mRunningAccounts;
-            if (accounts.length == 0) {
-                if (isLoggable) {
-                    Log.v(TAG, "scheduleSync: no accounts configured, dropping");
-                }
-                return;
-            }
-        }
-
-        final boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
-        final boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
-        if (manualSync) {
-            extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
-            extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
-        }
         final boolean ignoreSettings =
                 extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
-
-        int source;
-        if (uploadOnly) {
-            source = SyncStorageEngine.SOURCE_LOCAL;
-        } else if (manualSync) {
-            source = SyncStorageEngine.SOURCE_USER;
-        } else if (requestedAuthority == null) {
-            source = SyncStorageEngine.SOURCE_POLL;
+        int source = SyncStorageEngine.SOURCE_SERVICE;
+        int isEnabled = getIsTargetServiceActive(cname, userId);
+        // Only schedule this sync if
+        //   - we've explicitly been told to ignore settings.
+        //   - global sync is enabled for this user.
+        boolean syncAllowed =
+                ignoreSettings
+                || mSyncStorageEngine.getMasterSyncAutomatically(userId);
+        if (!syncAllowed) {
+            if (isLoggable) {
+                Log.d(TAG, "scheduleSync: sync of " + cname + " not allowed, dropping request.");
+            }
+            return;
+        }
+        if (isEnabled == 0) {
+            if (isLoggable) {
+                Log.d(TAG, "scheduleSync: " + cname + " is not enabled, dropping request");
+            }
+            return;
+        }
+        SyncStorageEngine.EndPoint info = new SyncStorageEngine.EndPoint(cname, userId);
+        Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(info);
+        long delayUntil = mSyncStorageEngine.getDelayUntilTime(info);
+        final long backoffTime = backoff != null ? backoff.first : 0;
+        if (isEnabled < 0) {
+            // Initialisation sync.
+            Bundle newExtras = new Bundle();
+            newExtras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
+            if (isLoggable) {
+                Log.v(TAG, "schedule initialisation Sync:"
+                        + ", delay until " + delayUntil
+                        + ", run now "
+                        + ", source " + source
+                        + ", sync service " + cname
+                        + ", extras " + newExtras);
+            }
+            scheduleSyncOperation(
+                    new SyncOperation(cname, userId, uid, source, newExtras,
+                            0 /* runtime */,
+                            0 /* flextime */,
+                            backoffTime,
+                            delayUntil));
         } else {
-            // this isn't strictly server, since arbitrary callers can (and do) request
-            // a non-forced two-way sync on a specific url
-            source = SyncStorageEngine.SOURCE_SERVER;
+            if (isLoggable) {
+                Log.v(TAG, "schedule Sync:"
+                        + ", delay until " + delayUntil
+                        + ", run by " + runtimeMillis
+                        + ", flex " + beforeRunTimeMillis
+                        + ", source " + source
+                        + ", sync service " + cname
+                        + ", extras " + extras);
+            }
+            scheduleSyncOperation(
+                    new SyncOperation(cname, userId, uid, source, extras,
+                            runtimeMillis /* runtime */,
+                            beforeRunTimeMillis /* flextime */,
+                            backoffTime,
+                            delayUntil));
         }
 
-        for (AccountAndUser account : accounts) {
-            // Compile a list of authorities that have sync adapters.
-            // For each authority sync each account that matches a sync adapter.
-            final HashSet<String> syncableAuthorities = new HashSet<String>();
-            for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter :
-                    mSyncAdapters.getAllServices(account.userId)) {
-                syncableAuthorities.add(syncAdapter.type.authority);
-            }
-
-            // if the url was specified then replace the list of authorities
-            // with just this authority or clear it if this authority isn't
-            // syncable
-            if (requestedAuthority != null) {
-                final boolean hasSyncAdapter = syncableAuthorities.contains(requestedAuthority);
-                syncableAuthorities.clear();
-                if (hasSyncAdapter) syncableAuthorities.add(requestedAuthority);
-            }
-
-            for (String authority : syncableAuthorities) {
-                int isSyncable = getIsSyncable(account.account, account.userId,
-                        authority);
-                if (isSyncable == 0) {
-                    continue;
-                }
-                final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
-                syncAdapterInfo = mSyncAdapters.getServiceInfo(
-                        SyncAdapterType.newKey(authority, account.account.type), account.userId);
-                if (syncAdapterInfo == null) {
-                    continue;
-                }
-                final boolean allowParallelSyncs = syncAdapterInfo.type.allowParallelSyncs();
-                final boolean isAlwaysSyncable = syncAdapterInfo.type.isAlwaysSyncable();
-                if (isSyncable < 0 && isAlwaysSyncable) {
-                    mSyncStorageEngine.setIsSyncable(account.account, account.userId, authority, 1);
-                    isSyncable = 1;
-                }
-                if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) {
-                    continue;
-                }
-                if (!syncAdapterInfo.type.supportsUploading() && uploadOnly) {
-                    continue;
-                }
-
-                // always allow if the isSyncable state is unknown
-                boolean syncAllowed =
-                        (isSyncable < 0)
-                        || ignoreSettings
-                        || (backgroundDataUsageAllowed
-                                && mSyncStorageEngine.getMasterSyncAutomatically(account.userId)
-                                && mSyncStorageEngine.getSyncAutomatically(account.account,
-                                        account.userId, authority));
-                if (!syncAllowed) {
-                    if (isLoggable) {
-                        Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority
-                                + " is not allowed, dropping request");
-                    }
-                    continue;
-                }
-
-                Pair<Long, Long> backoff = mSyncStorageEngine
-                        .getBackoff(account.account, account.userId, authority);
-                long delayUntil = mSyncStorageEngine.getDelayUntilTime(account.account,
-                        account.userId, authority);
-                final long backoffTime = backoff != null ? backoff.first : 0;
-                if (isSyncable < 0) {
-                    // Initialisation sync.
-                    Bundle newExtras = new Bundle();
-                    newExtras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
-                    if (isLoggable) {
-                        Log.v(TAG, "schedule initialisation Sync:"
-                                + ", delay until " + delayUntil
-                                + ", run by " + 0
-                                + ", source " + source
-                                + ", account " + account
-                                + ", authority " + authority
-                                + ", extras " + newExtras);
-                    }
-                    scheduleSyncOperation(
-                            new SyncOperation(account.account, account.userId, reason, source,
-                                    authority, newExtras, 0 /* immediate , 0 /* No flex time,
-                                    backoffTime, delayUntil, allowParallelSyncs));
-                }
-                if (!onlyThoseWithUnkownSyncableState) {
-                    if (isLoggable) {
-                        Log.v(TAG, "scheduleSync:"
-                                + " delay until " + delayUntil
-                                + " run by " + runtimeMillis
-                                + " flex " + beforeRuntimeMillis
-                                + ", source " + source
-                                + ", account " + account
-                                + ", authority " + authority
-                                + ", extras " + extras);
-                    }
-                    scheduleSyncOperation(
-                            new SyncOperation(account.account, account.userId, reason, source,
-                                    authority, extras, runtimeMillis, beforeRuntimeMillis,
-                                    backoffTime, delayUntil, allowParallelSyncs));
-                }
-            }
-        }*/
     }
 
     /**
@@ -755,9 +671,6 @@
             long runtimeMillis, boolean onlyThoseWithUnkownSyncableState) {
         boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
 
-        final boolean backgroundDataUsageAllowed = !mBootCompleted ||
-                getConnectivityManager().getBackgroundDataSetting();
-
         if (extras == null) {
             extras = new Bundle();
         }
@@ -774,8 +687,6 @@
         if (requestedAccount != null && userId != UserHandle.USER_ALL) {
             accounts = new AccountAndUser[] { new AccountAndUser(requestedAccount, userId) };
         } else {
-            // if the accounts aren't configured yet then we can't support an account-less
-            // sync request
             accounts = mRunningAccounts;
             if (accounts.length == 0) {
                 if (isLoggable) {
@@ -850,12 +761,10 @@
                     continue;
                 }
 
-                // always allow if the isSyncable state is unknown
                 boolean syncAllowed =
-                        (isSyncable < 0)
+                        (isSyncable < 0) // always allow if the isSyncable state is unknown
                         || ignoreSettings
-                        || (backgroundDataUsageAllowed
-                                && mSyncStorageEngine.getMasterSyncAutomatically(account.userId)
+                        || (mSyncStorageEngine.getMasterSyncAutomatically(account.userId)
                                 && mSyncStorageEngine.getSyncAutomatically(account.account,
                                         account.userId, authority));
                 if (!syncAllowed) {
@@ -865,11 +774,12 @@
                     }
                     continue;
                 }
-
-                Pair<Long, Long> backoff = mSyncStorageEngine
-                        .getBackoff(account.account, account.userId, authority);
-                long delayUntil = mSyncStorageEngine.getDelayUntilTime(account.account,
-                        account.userId, authority);
+                SyncStorageEngine.EndPoint info =
+                        new SyncStorageEngine.EndPoint(
+                                account.account, authority, account.userId);
+                Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(info);
+                long delayUntil =
+                        mSyncStorageEngine.getDelayUntilTime(info);
                 final long backoffTime = backoff != null ? backoff.first : 0;
                 if (isSyncable < 0) {
                     // Initialisation sync.
@@ -879,6 +789,7 @@
                         Log.v(TAG, "schedule initialisation Sync:"
                                 + ", delay until " + delayUntil
                                 + ", run by " + 0
+                                + ", flex " + 0
                                 + ", source " + source
                                 + ", account " + account
                                 + ", authority " + authority
@@ -954,13 +865,12 @@
         mSyncHandler.sendMessage(msg);
     }
 
-    private void sendCancelSyncsMessage(final Account account, final int userId,
-            final String authority) {
+    private void sendCancelSyncsMessage(final SyncStorageEngine.EndPoint info, Bundle extras) {
         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);
-        msg.arg1 = userId;
+        msg.setData(extras);
+        msg.obj = info;
         mSyncHandler.sendMessage(msg);
     }
 
@@ -983,10 +893,11 @@
     }
 
     private void clearBackoffSetting(SyncOperation op) {
-        mSyncStorageEngine.setBackoff(op.account, op.userId, op.authority,
-                SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
+        mSyncStorageEngine.setBackoff(op.target,
+                SyncStorageEngine.NOT_IN_BACKOFF_MODE,
+                SyncStorageEngine.NOT_IN_BACKOFF_MODE);
         synchronized (mSyncQueue) {
-            mSyncQueue.onBackoffChanged(op.account, op.userId, op.authority, 0);
+            mSyncQueue.onBackoffChanged(op.target, 0);
         }
     }
 
@@ -996,7 +907,7 @@
         final long now = SystemClock.elapsedRealtime();
 
         final Pair<Long, Long> previousSettings =
-                mSyncStorageEngine.getBackoff(op.account, op.userId, op.authority);
+                mSyncStorageEngine.getBackoff(op.target);
         long newDelayInMs = -1;
         if (previousSettings != null) {
             // don't increase backoff before current backoff is expired. This will happen for op's
@@ -1027,14 +938,12 @@
 
         final long backoff = now + newDelayInMs;
 
-        mSyncStorageEngine.setBackoff(op.account, op.userId, op.authority,
-                backoff, newDelayInMs);
-
+        mSyncStorageEngine.setBackoff(op.target, backoff, newDelayInMs);
         op.backoff = backoff;
         op.updateEffectiveRunTime();
 
         synchronized (mSyncQueue) {
-            mSyncQueue.onBackoffChanged(op.account, op.userId, op.authority, backoff);
+            mSyncQueue.onBackoffChanged(op.target, backoff);
         }
     }
 
@@ -1047,20 +956,20 @@
         } else {
             newDelayUntilTime = 0;
         }
-        mSyncStorageEngine
-                .setDelayUntilTime(op.account, op.userId, op.authority, newDelayUntilTime);
+        mSyncStorageEngine.setDelayUntilTime(op.target, newDelayUntilTime);
         synchronized (mSyncQueue) {
-            mSyncQueue.onDelayUntilTimeChanged(op.account, op.authority, newDelayUntilTime);
+            mSyncQueue.onDelayUntilTimeChanged(op.target, newDelayUntilTime);
         }
     }
 
     /**
      * Cancel the active sync if it matches the authority and account.
-     * @param account limit the cancelations to syncs with this account, if non-null
-     * @param authority limit the cancelations to syncs with this authority, if non-null
+     * @param info object containing info about which syncs to cancel. The authority can
+     * have null account/provider info to specify all accounts/providers.
+     * @param extras if non-null, specifies the exact sync to remove.
      */
-    public void cancelActiveSync(Account account, int userId, String authority) {
-        sendCancelSyncsMessage(account, userId, authority);
+    public void cancelActiveSync(SyncStorageEngine.EndPoint info, Bundle extras) {
+        sendCancelSyncsMessage(info, extras);
     }
 
     /**
@@ -1089,24 +998,40 @@
 
     /**
      * Remove scheduled sync operations.
-     * @param account limit the removals to operations with this account, if non-null
-     * @param authority limit the removals to operations with this authority, if non-null
+     * @param info limit the removals to operations that match this authority. The authority can
+     * have null account/provider info to specify all accounts/providers.
      */
-    public void clearScheduledSyncOperations(Account account, int userId, String authority) {
+    public void clearScheduledSyncOperations(SyncStorageEngine.EndPoint info) {
         synchronized (mSyncQueue) {
-            mSyncQueue.remove(account, userId, authority);
+            mSyncQueue.remove(info, null /* all operations */);
         }
-        mSyncStorageEngine.setBackoff(account, userId, authority,
+        mSyncStorageEngine.setBackoff(info,
                 SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
     }
 
+    /**
+     * Remove a specified sync, if it exists.
+     * @param info Authority for which the sync is to be removed.
+     * @param extras extras bundle to uniquely identify sync.
+     */
+    public void cancelScheduledSyncOperation(SyncStorageEngine.EndPoint info, Bundle extras) {
+        synchronized (mSyncQueue) {
+            mSyncQueue.remove(info, extras);
+        }
+        // Reset the back-off if there are no more syncs pending.
+        if (!mSyncStorageEngine.isSyncPending(info)) {
+            mSyncStorageEngine.setBackoff(info,
+                    SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
+        }
+    }
+
     void maybeRescheduleSync(SyncResult syncResult, SyncOperation operation) {
         boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
         if (isLoggable) {
             Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", " + operation);
         }
 
-        operation = new SyncOperation(operation);
+        operation = new SyncOperation(operation, 0L /* newRunTimeFromNow */);
 
         // The SYNC_EXTRAS_IGNORE_BACKOFF only applies to the first attempt to sync a given
         // request. Retries of the request will always honor the backoff, so clear the
@@ -1115,25 +1040,29 @@
             operation.extras.remove(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
         }
 
-        // If this sync aborted because the internal sync loop retried too many times then
-        //   don't reschedule. Otherwise we risk getting into a retry loop.
-        // If the operation succeeded to some extent then retry immediately.
-        // If this was a two-way sync then retry soft errors with an exponential backoff.
-        // If this was an upward sync then schedule a two-way sync immediately.
-        // Otherwise do not reschedule.
         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);
+            if (isLoggable) {
+                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)
                 && !syncResult.syncAlreadyInProgress) {
+            // If this was an upward sync then schedule a two-way sync immediately.
             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);
+            if (isLoggable) {
+                Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
+                        + "encountered an error: " + operation);
+            }
             scheduleSyncOperation(operation);
         } else if (syncResult.tooManyRetries) {
-            Log.d(TAG, "not retrying sync operation because it retried too many times: "
-                    + operation);
+            // If this sync aborted because the internal sync loop retried too many times then
+            //   don't reschedule. Otherwise we risk getting into a retry loop.
+            if (isLoggable) {
+                Log.d(TAG, "not retrying sync operation because it retried too many times: "
+                        + operation);
+            }
         } else if (syncResult.madeSomeProgress()) {
+            // If the operation succeeded to some extent then retry immediately.
             if (isLoggable) {
                 Log.d(TAG, "retrying sync operation because even though it had an error "
                         + "it achieved some success");
@@ -1146,19 +1075,18 @@
             }
             scheduleSyncOperation(
                 new SyncOperation(
-                    operation.account, operation.userId,
-                    operation.reason,
-                    operation.syncSource,
-                    operation.authority, operation.extras,
-                    DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000, operation.flexTime,
-                    operation.backoff, operation.delayUntil, operation.allowParallelSyncs));
+                        operation,
+                        DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000 /* newRunTimeFromNow */)
+                );
         } else if (syncResult.hasSoftError()) {
+            // If this was a two-way sync then retry soft errors with an exponential backoff.
             if (isLoggable) {
                 Log.d(TAG, "retrying sync operation because it encountered a soft error: "
                         + operation);
             }
             scheduleSyncOperation(operation);
         } else {
+            // Otherwise do not reschedule.
             Log.d(TAG, "not retrying sync operation because the error is a hard error: "
                     + operation);
         }
@@ -1191,9 +1119,12 @@
         updateRunningAccounts();
 
         cancelActiveSync(
-                null /* any account */,
-                userId,
-                null /* any authority */);
+                new SyncStorageEngine.EndPoint(
+                        null /* any account */,
+                        null /* any authority */,
+                        userId),
+                        null /* any sync. */
+                );
     }
 
     private void onUserRemoved(int userId) {
@@ -1202,7 +1133,7 @@
         // Clean up the storage engine database
         mSyncStorageEngine.doDatabaseCleanup(new Account[0], userId);
         synchronized (mSyncQueue) {
-            mSyncQueue.removeUser(userId);
+            mSyncQueue.removeUserLocked(userId);
         }
     }
 
@@ -1214,6 +1145,7 @@
         final SyncOperation mSyncOperation;
         final long mHistoryRowId;
         ISyncAdapter mSyncAdapter;
+        ISyncServiceAdapter mSyncServiceAdapter;
         final long mStartTime;
         long mTimeoutStartTime;
         boolean mBound;
@@ -1239,10 +1171,10 @@
             mSyncOperation = syncOperation;
             mHistoryRowId = historyRowId;
             mSyncAdapter = null;
+            mSyncServiceAdapter = null;
             mStartTime = SystemClock.elapsedRealtime();
             mTimeoutStartTime = mStartTime;
-            mSyncWakeLock = mSyncHandler.getSyncWakeLock(
-                    mSyncOperation.account, mSyncOperation.authority);
+            mSyncWakeLock = mSyncHandler.getSyncWakeLock(mSyncOperation);
             mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterUid));
             mSyncWakeLock.acquire();
         }
@@ -1269,7 +1201,12 @@
         public void onServiceConnected(ComponentName name, IBinder service) {
             Message msg = mSyncHandler.obtainMessage();
             msg.what = SyncHandler.MESSAGE_SERVICE_CONNECTED;
-            msg.obj = new ServiceConnectionData(this, ISyncAdapter.Stub.asInterface(service));
+            if (mSyncOperation.target.target_provider) {
+                msg.arg1 = SyncOperation.SYNC_TARGET_ADAPTER;
+            } else {
+                msg.arg1 = SyncOperation.SYNC_TARGET_SERVICE;
+            }
+            msg.obj = new ServiceConnectionData(this, service);
             mSyncHandler.sendMessage(msg);
         }
 
@@ -1280,13 +1217,13 @@
             mSyncHandler.sendMessage(msg);
         }
 
-        boolean bindToSyncAdapter(RegisteredServicesCache.ServiceInfo info, int userId) {
+        boolean bindToSyncAdapter(ComponentName serviceComponent, int userId) {
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Log.d(TAG, "bindToSyncAdapter: " + info.componentName + ", connection " + this);
+                Log.d(TAG, "bindToSyncAdapter: " + serviceComponent + ", connection " + this);
             }
             Intent intent = new Intent();
             intent.setAction("android.content.SyncAdapter");
-            intent.setComponent(info.componentName);
+            intent.setComponent(serviceComponent);
             intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
                     com.android.internal.R.string.sync_binding_label);
             intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser(
@@ -1296,7 +1233,7 @@
             final boolean bindResult = mContext.bindServiceAsUser(intent, this,
                     Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
                     | Context.BIND_ALLOW_OOM_MANAGEMENT,
-                    new UserHandle(mSyncOperation.userId));
+                    new UserHandle(mSyncOperation.target.userId));
             if (!bindResult) {
                 mBound = false;
             }
@@ -1319,7 +1256,6 @@
             mSyncWakeLock.setWorkSource(null);
         }
 
-        @Override
         public String toString() {
             StringBuilder sb = new StringBuilder();
             toString(sb);
@@ -1458,11 +1394,14 @@
                 int row = table.getNumRows();
                 Pair<AuthorityInfo, SyncStatusInfo> syncAuthoritySyncStatus = 
                         mSyncStorageEngine.getCopyOfAuthorityWithSyncStatus(
-                                account.account, account.userId, syncAdapterType.type.authority);
+                                new SyncStorageEngine.EndPoint(
+                                        account.account,
+                                        syncAdapterType.type.authority,
+                                        account.userId));
                 SyncStorageEngine.AuthorityInfo settings = syncAuthoritySyncStatus.first;
                 SyncStatusInfo status = syncAuthoritySyncStatus.second;
 
-                String authority = settings.authority;
+                String authority = settings.base.provider;
                 if (authority.length() > 50) {
                     authority = authority.substring(authority.length() - 50);
                 }
@@ -1583,14 +1522,25 @@
             int maxAuthority = 0;
             int maxAccount = 0;
             for (SyncStorageEngine.SyncHistoryItem item : items) {
-                SyncStorageEngine.AuthorityInfo authority
+                SyncStorageEngine.AuthorityInfo authorityInfo
                         = mSyncStorageEngine.getAuthority(item.authorityId);
                 final String authorityName;
                 final String accountKey;
-                if (authority != null) {
-                    authorityName = authority.authority;
-                    accountKey = authority.account.name + "/" + authority.account.type
-                            + " u" + authority.userId;
+                if (authorityInfo != null) {
+                    if (authorityInfo.base.target_provider) {
+                        authorityName = authorityInfo.base.provider;
+                        accountKey = authorityInfo.base.account.name + "/"
+                                + authorityInfo.base.account.type
+                                + " u" + authorityInfo.base.userId;
+                    } else if (authorityInfo.base.target_service) {
+                        authorityName = authorityInfo.base.service.getPackageName() + "/"
+                                + authorityInfo.base.service.getClassName()
+                                + " u" + authorityInfo.base.userId;
+                        accountKey = "no account";
+                    } else {
+                        authorityName = "Unknown";
+                        accountKey = "Unknown";
+                    }
                 } else {
                     authorityName = "Unknown";
                     accountKey = "Unknown";
@@ -1711,14 +1661,25 @@
             final PackageManager pm = mContext.getPackageManager();
             for (int i = 0; i < N; i++) {
                 SyncStorageEngine.SyncHistoryItem item = items.get(i);
-                SyncStorageEngine.AuthorityInfo authority
+                SyncStorageEngine.AuthorityInfo authorityInfo
                         = mSyncStorageEngine.getAuthority(item.authorityId);
                 final String authorityName;
                 final String accountKey;
-                if (authority != null) {
-                    authorityName = authority.authority;
-                    accountKey = authority.account.name + "/" + authority.account.type
-                            + " u" + authority.userId;
+                if (authorityInfo != null) {
+                    if (authorityInfo.base.target_provider) {
+                        authorityName = authorityInfo.base.provider;
+                        accountKey = authorityInfo.base.account.name + "/"
+                                + authorityInfo.base.account.type
+                                + " u" + authorityInfo.base.userId;
+                    } else if (authorityInfo.base.target_service) {
+                        authorityName = authorityInfo.base.service.getPackageName() + "/"
+                                + authorityInfo.base.service.getClassName()
+                                + " u" + authorityInfo.base.userId;
+                        accountKey = "none";
+                    } else {
+                        authorityName = "Unknown";
+                        accountKey = "Unknown";
+                    }
                 } else {
                     authorityName = "Unknown";
                     accountKey = "Unknown";
@@ -1777,14 +1738,25 @@
                 if (extras == null || extras.size() == 0) {
                     continue;
                 }
-                final SyncStorageEngine.AuthorityInfo authority
+                final SyncStorageEngine.AuthorityInfo authorityInfo
                         = mSyncStorageEngine.getAuthority(item.authorityId);
                 final String authorityName;
                 final String accountKey;
-                if (authority != null) {
-                    authorityName = authority.authority;
-                    accountKey = authority.account.name + "/" + authority.account.type
-                            + " u" + authority.userId;
+                if (authorityInfo != null) {
+                    if (authorityInfo.base.target_provider) {
+                        authorityName = authorityInfo.base.provider;
+                        accountKey = authorityInfo.base.account.name + "/"
+                                + authorityInfo.base.account.type
+                                + " u" + authorityInfo.base.userId;
+                    } else if (authorityInfo.base.target_service) {
+                        authorityName = authorityInfo.base.service.getPackageName() + "/"
+                                + authorityInfo.base.service.getClassName()
+                                + " u" + authorityInfo.base.userId;
+                        accountKey = "none";
+                    } else {
+                        authorityName = "Unknown";
+                        accountKey = "Unknown";
+                    }
                 } else {
                     authorityName = "Unknown";
                     accountKey = "Unknown";
@@ -1928,10 +1900,11 @@
 
     class ServiceConnectionData {
         public final ActiveSyncContext activeSyncContext;
-        public final ISyncAdapter syncAdapter;
-        ServiceConnectionData(ActiveSyncContext activeSyncContext, ISyncAdapter syncAdapter) {
+        public final IBinder adapter;
+
+        ServiceConnectionData(ActiveSyncContext activeSyncContext, IBinder adapter) {
             this.activeSyncContext = activeSyncContext;
-            this.syncAdapter = syncAdapter;
+            this.adapter = adapter;
         }
     }
 
@@ -1951,8 +1924,7 @@
         public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo();
         private Long mAlarmScheduleTime = null;
         public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker();
-        private final HashMap<Pair<Account, String>, PowerManager.WakeLock> mWakeLocks =
-                Maps.newHashMap();
+        private final HashMap<String, PowerManager.WakeLock> mWakeLocks = Maps.newHashMap();
 
         private volatile CountDownLatch mReadyToRunLatch = new CountDownLatch(1);
 
@@ -1966,12 +1938,11 @@
             }
         }
 
-        private PowerManager.WakeLock getSyncWakeLock(Account account, String authority) {
-            final Pair<Account, String> wakeLockKey = Pair.create(account, authority);
+        private PowerManager.WakeLock getSyncWakeLock(SyncOperation operation) {
+            final String wakeLockKey = operation.wakeLockKey();
             PowerManager.WakeLock wakeLock = mWakeLocks.get(wakeLockKey);
             if (wakeLock == null) {
-                final String name = SYNC_WAKE_LOCK_PREFIX + "/" + authority + "/" + account.type
-                        + "/" + account.name;
+                final String name = SYNC_WAKE_LOCK_PREFIX + operation.wakeLockName();
                 wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
                 wakeLock.setReferenceCounted(false);
                 mWakeLocks.put(wakeLockKey, wakeLock);
@@ -2020,7 +1991,6 @@
             super(looper);
         }
 
-        @Override
         public void handleMessage(Message msg) {
             long earliestFuturePollTime = Long.MAX_VALUE;
             long nextPendingSyncTime = Long.MAX_VALUE;
@@ -2038,12 +2008,13 @@
                 earliestFuturePollTime = scheduleReadyPeriodicSyncs();
                 switch (msg.what) {
                     case SyncHandler.MESSAGE_CANCEL: {
-                        Pair<Account, String> payload = (Pair<Account, String>) msg.obj;
+                        SyncStorageEngine.EndPoint payload = (SyncStorageEngine.EndPoint) msg.obj;
+                        Bundle extras = msg.peekData();
                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
                             Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CANCEL: "
-                                    + payload.first + ", " + payload.second);
+                                    + payload + " bundle: " + extras);
                         }
-                        cancelActiveSyncLocked(payload.first, msg.arg1, payload.second);
+                        cancelActiveSyncLocked(payload, extras);
                         nextPendingSyncTime = maybeStartNextSyncLocked();
                         break;
                     }
@@ -2052,35 +2023,39 @@
                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
                             Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_FINISHED");
                         }
-                        SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload)msg.obj;
+                        SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload) msg.obj;
                         if (!isSyncStillActive(payload.activeSyncContext)) {
                             Log.d(TAG, "handleSyncHandlerMessage: dropping since the "
                                     + "sync is no longer active: "
                                     + payload.activeSyncContext);
                             break;
                         }
-                        runSyncFinishedOrCanceledLocked(payload.syncResult, payload.activeSyncContext);
+                        runSyncFinishedOrCanceledLocked(payload.syncResult,
+                                payload.activeSyncContext);
 
                         // 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;
+                        ServiceConnectionData msgData = (ServiceConnectionData) msg.obj;
                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
                             Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CONNECTED: "
                                     + msgData.activeSyncContext);
                         }
                         // check that this isn't an old message
                         if (isSyncStillActive(msgData.activeSyncContext)) {
-                            runBoundToSyncAdapter(msgData.activeSyncContext, msgData.syncAdapter);
+                            runBoundToAdapter(
+                                    msgData.activeSyncContext,
+                                    msgData.adapter,
+                                    msg.arg1 /* target */);
                         }
                         break;
                     }
 
                     case SyncHandler.MESSAGE_SERVICE_DISCONNECTED: {
                         final ActiveSyncContext currentSyncContext =
-                                ((ServiceConnectionData)msg.obj).activeSyncContext;
+                                ((ServiceConnectionData) msg.obj).activeSyncContext;
                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
                             Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_DISCONNECTED: "
                                     + currentSyncContext);
@@ -2089,12 +2064,15 @@
                         if (isSyncStillActive(currentSyncContext)) {
                             // cancel the sync if we have a syncadapter, which means one is
                             // outstanding
-                            if (currentSyncContext.mSyncAdapter != null) {
-                                try {
+                            try {
+                                if (currentSyncContext.mSyncAdapter != null) {
                                     currentSyncContext.mSyncAdapter.cancelSync(currentSyncContext);
-                                } catch (RemoteException e) {
-                                    // we don't need to retry this in this case
+                                } else if (currentSyncContext.mSyncServiceAdapter != null) {
+                                    currentSyncContext.mSyncServiceAdapter
+                                        .cancelSync(currentSyncContext);
                                 }
+                            } catch (RemoteException e) {
+                                // We don't need to retry this in this case.
                             }
 
                             // pretend that the sync failed with an IOException,
@@ -2139,9 +2117,46 @@
             }
         }
 
+        private boolean isDispatchable(SyncStorageEngine.EndPoint target) {
+            final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+            if (target.target_provider) {
+                // skip the sync if the account of this operation no longer exists
+                AccountAndUser[] accounts = mRunningAccounts;
+                if (!containsAccountAndUser(
+                        accounts, target.account, target.userId)) {
+                    return false;
+                }
+                if (!mSyncStorageEngine.getMasterSyncAutomatically(target.userId)
+                        || !mSyncStorageEngine.getSyncAutomatically(
+                                target.account,
+                                target.userId,
+                                target.provider)) {
+                    if (isLoggable) {
+                        Log.v(TAG, "    Not scheduling periodic operation: sync turned off.");
+                    }
+                    return false;
+                }
+                if (getIsSyncable(target.account, target.userId, target.provider)
+                        == 0) {
+                    if (isLoggable) {
+                        Log.v(TAG, "    Not scheduling periodic operation: isSyncable == 0.");
+                    }
+                    return false;
+                }
+            } else if (target.target_service) {
+                if (getIsTargetServiceActive(target.service, target.userId) == 0) {
+                    if (isLoggable) {
+                        Log.v(TAG, "   Not scheduling periodic operation: isEnabled == 0.");
+                    }
+                    return false;
+                }
+            }
+            return true;
+        }
+
         /**
          * 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,
+         * @return the desired start time of the earliest future periodic sync operation,
          * in milliseconds since boot
          */
         private long scheduleReadyPeriodicSyncs() {
@@ -2149,14 +2164,7 @@
             if (isLoggable) {
                 Log.v(TAG, "scheduleReadyPeriodicSyncs");
             }
-            final boolean backgroundDataUsageAllowed =
-                    getConnectivityManager().getBackgroundDataSetting();
             long earliestFuturePollTime = Long.MAX_VALUE;
-            if (!backgroundDataUsageAllowed) {
-                return earliestFuturePollTime;
-            }
-
-            AccountAndUser[] accounts = mRunningAccounts;
 
             final long nowAbsolute = System.currentTimeMillis();
             final long shiftedNowAbsolute = (0 < nowAbsolute - mSyncRandomOffsetMillis)
@@ -2167,23 +2175,8 @@
             for (Pair<AuthorityInfo, SyncStatusInfo> info : infos) {
                 final AuthorityInfo authorityInfo = info.first;
                 final SyncStatusInfo status = info.second;
-                // skip the sync if the account of this operation no longer
-                // exists
-                if (!containsAccountAndUser(
-                        accounts, authorityInfo.account, authorityInfo.userId)) {
-                    continue;
-                }
-
-                if (!mSyncStorageEngine.getMasterSyncAutomatically(authorityInfo.userId)
-                        || !mSyncStorageEngine.getSyncAutomatically(
-                                authorityInfo.account, authorityInfo.userId,
-                                authorityInfo.authority)) {
-                    continue;
-                }
-
-                if (getIsSyncable(
-                        authorityInfo.account, authorityInfo.userId, authorityInfo.authority)
-                        == 0) {
+                
+                if (!isDispatchable(authorityInfo.base)) {
                     continue;
                 }
 
@@ -2211,7 +2204,7 @@
                     boolean runEarly = remainingMillis <= flexInMillis
                             && timeSinceLastRunMillis > periodInMillis - flexInMillis;
                     if (isLoggable) {
-                        Log.v(TAG, "sync: " + i + " for " + authorityInfo.authority + "."
+                        Log.v(TAG, "sync: " + i + " for " + authorityInfo.base + "."
                         + " period: " + (periodInMillis)
                         + " flex: " + (flexInMillis)
                         + " remaining: " + (remainingMillis)
@@ -2231,41 +2224,49 @@
                      * future, sync now and reinitialize. This can happen for
                      * example if the user changed the time, synced and changed
                      * back.
-                     * Case 3: If we failed to sync at the last scheduled
-                     * time.
+                     * Case 3: If we failed to sync at the last scheduled time.
                      * Case 4: This sync is close enough to the time that we can schedule it.
                      */
-                    if (runEarly // Case 4
-                            || remainingMillis == periodInMillis // Case 1
+                    if (remainingMillis == periodInMillis // Case 1
                             || lastPollTimeAbsolute > nowAbsolute // Case 2
-                            || timeSinceLastRunMillis >= periodInMillis) { // Case 3
+                            || timeSinceLastRunMillis >= periodInMillis // Case 3
+                            || runEarly) { // Case 4
                         // Sync now
-                        
-                        final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(
-                                authorityInfo.account, authorityInfo.userId,
-                                authorityInfo.authority);
-                        final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
-                        syncAdapterInfo = mSyncAdapters.getServiceInfo(
-                                SyncAdapterType.newKey(
-                                        authorityInfo.authority, authorityInfo.account.type),
-                                authorityInfo.userId);
-                        if (syncAdapterInfo == null) {
-                            continue;
-                        }
+                        SyncStorageEngine.EndPoint target = authorityInfo.base;
+                        final Pair<Long, Long> backoff =
+                                mSyncStorageEngine.getBackoff(target);
                         mSyncStorageEngine.setPeriodicSyncTime(authorityInfo.ident,
                                 authorityInfo.periodicSyncs.get(i), nowAbsolute);
-                        scheduleSyncOperation(
-                                new SyncOperation(authorityInfo.account, authorityInfo.userId,
-                                        SyncOperation.REASON_PERIODIC,
-                                        SyncStorageEngine.SOURCE_PERIODIC,
-                                        authorityInfo.authority, extras,
-                                        0 /* runtime */, 0 /* flex */,
-                                                backoff != null ? backoff.first : 0,
-                                        mSyncStorageEngine.getDelayUntilTime(
-                                                authorityInfo.account, authorityInfo.userId,
-                                                authorityInfo.authority),
-                                        syncAdapterInfo.type.allowParallelSyncs()));
-                        
+
+                        if (target.target_provider) {
+                            final RegisteredServicesCache.ServiceInfo<SyncAdapterType>
+                                syncAdapterInfo = mSyncAdapters.getServiceInfo(
+                                    SyncAdapterType.newKey(
+                                            target.provider, target.account.type),
+                                    target.userId);
+                            if (syncAdapterInfo == null) {
+                                continue;
+                            }
+                            scheduleSyncOperation(
+                                    new SyncOperation(target.account, target.userId,
+                                            SyncOperation.REASON_PERIODIC,
+                                            SyncStorageEngine.SOURCE_PERIODIC,
+                                            target.provider, extras,
+                                            0 /* runtime */, 0 /* flex */,
+                                            backoff != null ? backoff.first : 0,
+                                            mSyncStorageEngine.getDelayUntilTime(target),
+                                            syncAdapterInfo.type.allowParallelSyncs()));
+                        } else if (target.target_service) {
+                            scheduleSyncOperation(
+                                    new SyncOperation(target.service, target.userId,
+                                            SyncOperation.REASON_PERIODIC,
+                                            SyncStorageEngine.SOURCE_PERIODIC,
+                                            extras,
+                                            0 /* runtime */,
+                                            0 /* flex */,
+                                            backoff != null ? backoff.first : 0,
+                                            mSyncStorageEngine.getDelayUntilTime(target)));
+                        }
                     }
                     // Compute when this periodic sync should next run.
                     long nextPollTimeAbsolute;
@@ -2312,8 +2313,7 @@
 
             // If the accounts aren't known yet then we aren't ready to run. We will be kicked
             // when the account lookup request does complete.
-            AccountAndUser[] accounts = mRunningAccounts;
-            if (accounts == INITIAL_ACCOUNTS_ARRAY) {
+            if (mRunningAccounts == INITIAL_ACCOUNTS_ARRAY) {
                 if (isLoggable) {
                     Log.v(TAG, "maybeStartNextSync: accounts not known, skipping");
                 }
@@ -2323,9 +2323,6 @@
             // 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.
-            final boolean backgroundDataUsageAllowed =
-                    getConnectivityManager().getBackgroundDataSetting();
-
             final long now = SystemClock.elapsedRealtime();
 
             // will be set to the next time that a sync should be considered for running
@@ -2347,40 +2344,23 @@
                 while (operationIterator.hasNext()) {
                     final SyncOperation op = operationIterator.next();
 
-                    // Drop the sync if the account of this operation no longer exists.
-                    if (!containsAccountAndUser(accounts, op.account, op.userId)) {
-                        operationIterator.remove();
-                        mSyncStorageEngine.deleteFromPending(op.pendingOperation);
-                        if (isLoggable) {
-                            Log.v(TAG, "    Dropping sync operation: account doesn't exist.");
-                        }
-                        continue;
-                    }
-
-                    // Drop this sync request if it isn't syncable.
-                    int syncableState = getIsSyncable(
-                            op.account, op.userId, op.authority);
-                    if (syncableState == 0) {
-                        operationIterator.remove();
-                        mSyncStorageEngine.deleteFromPending(op.pendingOperation);
-                        if (isLoggable) {
-                            Log.v(TAG, "    Dropping sync operation: isSyncable == 0.");
-                        }
-                        continue;
-                    }
-
-                    // If the user is not running, drop the request.
-                    if (!activityManager.isUserRunning(op.userId)) {
-                        final UserInfo userInfo = mUserManager.getUserInfo(op.userId);
+                    // If the user is not running, skip the request.
+                    if (!activityManager.isUserRunning(op.target.userId)) {
+                        final UserInfo userInfo = mUserManager.getUserInfo(op.target.userId);
                         if (userInfo == null) {
-                            removedUsers.add(op.userId);
+                            removedUsers.add(op.target.userId);
                         }
                         if (isLoggable) {
-                            Log.v(TAG, "    Dropping sync operation: user not running.");
+                            Log.v(TAG, "    Dropping all sync operations for + "
+                                    + op.target.userId + ": user not running.");
                         }
                         continue;
                     }
-
+                    if (!isOperationValidLocked(op)) {
+                        operationIterator.remove();
+                        mSyncStorageEngine.deleteFromPending(op.pendingOperation);
+                        continue;
+                    }
                     // If the next run time is in the future, even given the flexible scheduling,
                     // return the time.
                     if (op.effectiveRunTime - op.flexTime > now) {
@@ -2388,42 +2368,16 @@
                             nextReadyToRunTime = op.effectiveRunTime;
                         }
                         if (isLoggable) {
-                            Log.v(TAG, "    Dropping sync operation: Sync too far in future.");
+                            Log.v(TAG, "    Not running sync operation: Sync too far in future."
+                                    + "effective: " + op.effectiveRunTime + " flex: " + op.flexTime
+                                    + " now: " + now);
                         }
                         continue;
                     }
-                    // TODO: change this behaviour for non-registered syncs.
-                    final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
-                    syncAdapterInfo = mSyncAdapters.getServiceInfo(
-                            SyncAdapterType.newKey(op.authority, op.account.type), op.userId);
-
-                    // only proceed if network is connected for requesting UID
-                    final boolean uidNetworkConnected;
-                    if (syncAdapterInfo != null) {
-                        final NetworkInfo networkInfo = getConnectivityManager()
-                                .getActiveNetworkInfoForUid(syncAdapterInfo.uid);
-                        uidNetworkConnected = networkInfo != null && networkInfo.isConnected();
-                    } else {
-                        uidNetworkConnected = false;
-                    }
-
-                    // skip the sync if it isn't manual, and auto sync or
-                    // background data usage is disabled or network is
-                    // disconnected for the target UID.
-                    if (!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false)
-                            && (syncableState > 0)
-                            && (!mSyncStorageEngine.getMasterSyncAutomatically(op.userId)
-                                || !backgroundDataUsageAllowed
-                                || !uidNetworkConnected
-                                || !mSyncStorageEngine.getSyncAutomatically(
-                                       op.account, op.userId, op.authority))) {
-                        operationIterator.remove();
-                        mSyncStorageEngine.deleteFromPending(op.pendingOperation);
-                        continue;
-                    }
-
+                    // Add this sync to be run.
                     operations.add(op);
                 }
+
                 for (Integer user : removedUsers) {
                     // if it's still removed
                     if (mUserManager.getUserInfo(user) == null) {
@@ -2465,13 +2419,9 @@
                             }
                         }
                     }
-                    if (activeOp.account.type.equals(candidate.account.type)
-                            && activeOp.authority.equals(candidate.authority)
-                            && activeOp.userId == candidate.userId
-                            && (!activeOp.allowParallelSyncs
-                                || activeOp.account.name.equals(candidate.account.name))) {
+                    if (activeOp.isConflict(candidate)) {
                         conflict = activeSyncContext;
-                        // don't break out since we want to do a full count of the varieties
+                        // 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) {
@@ -2521,8 +2471,8 @@
                     // 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);
+                        Log.v(TAG, "canceling and rescheduling sync since an expedited is ready to"
+                                + " run, " + oldestNonExpeditedRegular);
                     }
                 } else if (longRunning != null
                         && (candidateIsInitialization
@@ -2551,7 +2501,113 @@
             }
 
             return nextReadyToRunTime;
-     }
+        }
+
+        /**
+         * Determine if a sync is no longer valid and should be dropped from the sync queue and its
+         * pending op deleted.
+         * @param op operation for which the sync is to be scheduled.
+         */
+        private boolean isOperationValidLocked(SyncOperation op) {
+            final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+            int targetUid;
+            int state;
+            final SyncStorageEngine.EndPoint target = op.target;
+            boolean syncEnabled = mSyncStorageEngine.getMasterSyncAutomatically(target.userId);
+            if (target.target_provider) {
+                // Drop the sync if the account of this operation no longer exists.
+                AccountAndUser[] accounts = mRunningAccounts;
+                if (!containsAccountAndUser(accounts, target.account, target.userId)) {
+                    if (isLoggable) {
+                        Log.v(TAG, "    Dropping sync operation: account doesn't exist.");
+                    }
+                    return false;
+                }
+                // Drop this sync request if it isn't syncable.
+                state = getIsSyncable(target.account, target.userId, target.provider);
+                if (state == 0) {
+                    if (isLoggable) {
+                        Log.v(TAG, "    Dropping sync operation: isSyncable == 0.");
+                    }
+                    return false;
+                }
+                syncEnabled = syncEnabled && mSyncStorageEngine.getSyncAutomatically(
+                        target.account, target.userId, target.provider);
+
+                final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
+                syncAdapterInfo = mSyncAdapters.getServiceInfo(
+                        SyncAdapterType.newKey(
+                                target.provider, target.account.type), target.userId);
+                if (syncAdapterInfo != null) {
+                    targetUid = syncAdapterInfo.uid;
+                } else {
+                    if (isLoggable) {
+                        Log.v(TAG, "    Dropping sync operation: No sync adapter registered"
+                                + "for: " + target);
+                    }
+                    return false;
+                }
+            } else if (target.target_service) {
+                state = getIsTargetServiceActive(target.service, target.userId);
+                if (state == 0) {
+                    // TODO: Change this to not drop disabled syncs - keep them in the pending queue.
+                    if (isLoggable) {
+                        Log.v(TAG, "    Dropping sync operation: isActive == 0.");
+                    }
+                    return false;
+                }
+                try {
+                    targetUid = mContext.getPackageManager()
+                            .getServiceInfo(target.service, 0)
+                            .applicationInfo
+                            .uid;
+                } catch (PackageManager.NameNotFoundException e) {
+                    if (isLoggable) {
+                        Log.v(TAG, "    Dropping sync operation: No service registered for: "
+                                + target.service);
+                    }
+                    return false;
+                }
+            } else {
+                Log.e(TAG, "Unknown target for Sync Op: " + target);
+                return false;
+            }
+
+            // We ignore system settings that specify the sync is invalid if:
+            // 1) It's manual - we try it anyway. When/if it fails it will be rescheduled.
+            //      or
+            // 2) it's an initialisation sync - we just need to connect to it.
+            final boolean ignoreSystemConfiguration =
+                    op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false)
+                    || (state < 0);
+
+            // Sync not enabled.
+            if (!syncEnabled && !ignoreSystemConfiguration) {
+                if (isLoggable) {
+                    Log.v(TAG, "    Dropping sync operation: disallowed by settings/network.");
+                }
+                return false;
+            }
+            // Network down.
+            final NetworkInfo networkInfo = getConnectivityManager()
+                    .getActiveNetworkInfoForUid(targetUid);
+            final boolean uidNetworkConnected = networkInfo != null && networkInfo.isConnected();
+            if (!uidNetworkConnected && !ignoreSystemConfiguration) {
+                if (isLoggable) {
+                    Log.v(TAG, "    Dropping sync operation: disallowed by settings/network.");
+                }
+                return false;
+            }
+            // Metered network.
+            if (op.isNotAllowedOnMetered() && getConnectivityManager().isActiveNetworkMetered()
+                    && !ignoreSystemConfiguration) {
+                if (isLoggable) {
+                    Log.v(TAG, "    Dropping sync operation: not allowed on metered network.");
+                }
+                return false;
+            }
+            return true;
+        }
 
         private boolean dispatchSyncOperation(SyncOperation op) {
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -2561,27 +2617,48 @@
                     Log.v(TAG, syncContext.toString());
                 }
             }
-
-            // connect to the sync adapter
-            SyncAdapterType syncAdapterType = SyncAdapterType.newKey(op.authority, op.account.type);
-            final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
-            syncAdapterInfo = mSyncAdapters.getServiceInfo(syncAdapterType, op.userId);
-            if (syncAdapterInfo == null) {
-                Log.d(TAG, "can't find a sync adapter for " + syncAdapterType
-                        + ", removing settings for it");
-                mSyncStorageEngine.removeAuthority(op.account, op.userId, op.authority);
-                return false;
+            // Connect to the sync adapter.
+            int targetUid;
+            ComponentName targetComponent;
+            final SyncStorageEngine.EndPoint info = op.target;
+            if (info.target_provider) {
+                SyncAdapterType syncAdapterType =
+                        SyncAdapterType.newKey(info.provider, info.account.type);
+                final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
+                syncAdapterInfo = mSyncAdapters.getServiceInfo(syncAdapterType, info.userId);
+                if (syncAdapterInfo == null) {
+                    Log.d(TAG, "can't find a sync adapter for " + syncAdapterType
+                            + ", removing settings for it");
+                    mSyncStorageEngine.removeAuthority(info);
+                    return false;
+                }
+                targetUid = syncAdapterInfo.uid;
+                targetComponent = syncAdapterInfo.componentName;
+            } else {
+                // TODO: Store the uid of the service as part of the authority info in order to
+                // avoid this call?
+                try {
+                    targetUid = mContext.getPackageManager()
+                            .getServiceInfo(info.service, 0)
+                            .applicationInfo
+                            .uid;
+                    targetComponent = info.service;
+                } catch(PackageManager.NameNotFoundException e) {
+                    Log.d(TAG, "Can't find a service for " + info.service
+                            + ", removing settings for it");
+                    mSyncStorageEngine.removeAuthority(info);
+                    return false;
+                }
             }
-
             ActiveSyncContext activeSyncContext =
-                    new ActiveSyncContext(op, insertStartSyncEvent(op), syncAdapterInfo.uid);
+                    new ActiveSyncContext(op, insertStartSyncEvent(op), targetUid);
             activeSyncContext.mSyncInfo = mSyncStorageEngine.addActiveSync(activeSyncContext);
             mActiveSyncContexts.add(activeSyncContext);
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "dispatchSyncOperation: starting " + activeSyncContext);
             }
-            if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo, op.userId)) {
-                Log.e(TAG, "Bind attempt failed to " + syncAdapterInfo);
+            if (!activeSyncContext.bindToSyncAdapter(targetComponent, info.userId)) {
+                Log.e(TAG, "Bind attempt failed - target: " + targetComponent);
                 closeActiveSyncContext(activeSyncContext);
                 return false;
             }
@@ -2589,47 +2666,54 @@
             return true;
         }
 
-        private void runBoundToSyncAdapter(final ActiveSyncContext activeSyncContext,
-              ISyncAdapter syncAdapter) {
-            activeSyncContext.mSyncAdapter = syncAdapter;
+        private void runBoundToAdapter(final ActiveSyncContext activeSyncContext,
+                IBinder syncAdapter, int target) {
             final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
             try {
                 activeSyncContext.mIsLinkedToDeath = true;
-                syncAdapter.asBinder().linkToDeath(activeSyncContext, 0);
+                syncAdapter.linkToDeath(activeSyncContext, 0);
 
-                syncAdapter.startSync(activeSyncContext, syncOperation.authority,
-                        syncOperation.account, syncOperation.extras);
+                if (syncOperation.target.target_provider) {
+                    activeSyncContext.mSyncAdapter = ISyncAdapter.Stub.asInterface(syncAdapter);
+                    activeSyncContext.mSyncAdapter
+                        .startSync(activeSyncContext, syncOperation.target.provider,
+                                syncOperation.target.account, syncOperation.extras);
+                } else if (syncOperation.target.target_service) {
+                    activeSyncContext.mSyncServiceAdapter =
+                            ISyncServiceAdapter.Stub.asInterface(syncAdapter);
+                    activeSyncContext.mSyncServiceAdapter
+                        .startSync(activeSyncContext, syncOperation.extras);
+                }
             } catch (RemoteException remoteExc) {
                 Log.d(TAG, "maybeStartNextSync: caught a RemoteException, rescheduling", remoteExc);
                 closeActiveSyncContext(activeSyncContext);
                 increaseBackoffSetting(syncOperation);
-                scheduleSyncOperation(new SyncOperation(syncOperation));
+                scheduleSyncOperation(
+                        new SyncOperation(syncOperation, 0L /* newRunTimeFromNow */));
             } catch (RuntimeException exc) {
                 closeActiveSyncContext(activeSyncContext);
                 Log.e(TAG, "Caught RuntimeException while starting the sync " + syncOperation, exc);
             }
         }
 
-        private void cancelActiveSyncLocked(Account account, int userId, String authority) {
+        /**
+         * Cancel the sync for the provided authority that matches the given bundle. Info here
+         * can have null fields to indicate all the active syncs for that field.
+         */
+        private void cancelActiveSyncLocked(SyncStorageEngine.EndPoint info, Bundle extras) {
             ArrayList<ActiveSyncContext> activeSyncs =
                     new ArrayList<ActiveSyncContext>(mActiveSyncContexts);
             for (ActiveSyncContext activeSyncContext : activeSyncs) {
                 if (activeSyncContext != null) {
-                    // if an account was specified then only cancel the sync if it matches
-                    if (account != null) {
-                        if (!account.equals(activeSyncContext.mSyncOperation.account)) {
-                            continue;
-                        }
+                    final SyncStorageEngine.EndPoint opInfo =
+                            activeSyncContext.mSyncOperation.target;
+                    if (!opInfo.matches(info)) {
+                        continue;
                     }
-                    // if an authority was specified then only cancel the sync if it matches
-                    if (authority != null) {
-                        if (!authority.equals(activeSyncContext.mSyncOperation.authority)) {
-                            continue;
-                        }
-                    }
-                    // check if the userid matches
-                    if (userId != UserHandle.USER_ALL
-                            && userId != activeSyncContext.mSyncOperation.userId) {
+                    if (extras != null &&
+                            !syncExtrasEquals(activeSyncContext.mSyncOperation.extras,
+                                    extras,
+                                    false /* no config settings */)) {
                         continue;
                     }
                     runSyncFinishedOrCanceledLocked(null /* no result since this is a cancel */,
@@ -2642,16 +2726,19 @@
                 ActiveSyncContext activeSyncContext) {
             boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
 
+            final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
+            final SyncStorageEngine.EndPoint info = syncOperation.target;
+
             if (activeSyncContext.mIsLinkedToDeath) {
-                activeSyncContext.mSyncAdapter.asBinder().unlinkToDeath(activeSyncContext, 0);
+                if (info.target_provider) {
+                    activeSyncContext.mSyncAdapter.asBinder().unlinkToDeath(activeSyncContext, 0);
+                } else {
+                    activeSyncContext.mSyncServiceAdapter.asBinder().unlinkToDeath(activeSyncContext, 0);
+                }
                 activeSyncContext.mIsLinkedToDeath = false;
             }
             closeActiveSyncContext(activeSyncContext);
-
-            final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
-
             final long elapsedTime = SystemClock.elapsedRealtime() - activeSyncContext.mStartTime;
-
             String historyMessage;
             int downstreamActivity;
             int upstreamActivity;
@@ -2693,6 +2780,12 @@
                     } catch (RemoteException e) {
                         // we don't need to retry this in this case
                     }
+                } else if (activeSyncContext.mSyncServiceAdapter != null) {
+                    try {
+                        activeSyncContext.mSyncServiceAdapter.cancelSync(activeSyncContext);
+                    } catch (RemoteException e) {
+                        // we don't need to retry this in this case
+                    }
                 }
                 historyMessage = SyncStorageEngine.MESG_CANCELED;
                 downstreamActivity = 0;
@@ -2702,24 +2795,35 @@
             stopSyncEvent(activeSyncContext.mHistoryRowId, syncOperation, historyMessage,
                     upstreamActivity, downstreamActivity, elapsedTime);
 
-            if (syncResult != null && syncResult.tooManyDeletions) {
-                installHandleTooManyDeletesNotification(syncOperation.account,
-                        syncOperation.authority, syncResult.stats.numDeletes,
-                        syncOperation.userId);
+            // Check for full-resync and schedule it after closing off the last sync.
+            if (info.target_provider) {
+                if (syncResult != null && syncResult.tooManyDeletions) {
+                    installHandleTooManyDeletesNotification(info.account,
+                            info.provider, syncResult.stats.numDeletes,
+                            info.userId);
+                } else {
+                    mNotificationMgr.cancelAsUser(null,
+                            info.account.hashCode() ^ info.provider.hashCode(),
+                            new UserHandle(info.userId));
+                }
+                if (syncResult != null && syncResult.fullSyncRequested) {
+                    scheduleSyncOperation(
+                            new SyncOperation(info.account, info.userId,
+                                syncOperation.reason,
+                                syncOperation.syncSource, info.provider, new Bundle(),
+                                0 /* delay */, 0 /* flex */,
+                                syncOperation.backoff, syncOperation.delayUntil,
+                                syncOperation.allowParallelSyncs));
+                }
             } else {
-                mNotificationMgr.cancelAsUser(null,
-                        syncOperation.account.hashCode() ^ syncOperation.authority.hashCode(),
-                        new UserHandle(syncOperation.userId));
-            }
-
-            if (syncResult != null && syncResult.fullSyncRequested) {
-                scheduleSyncOperation(
-                        new SyncOperation(syncOperation.account, syncOperation.userId,
-                            syncOperation.reason,
-                            syncOperation.syncSource, syncOperation.authority, new Bundle(),
-                            0 /* delay */, 0 /* flex */,
-                            syncOperation.backoff, syncOperation.delayUntil,
-                            syncOperation.allowParallelSyncs));
+                if (syncResult != null && syncResult.fullSyncRequested) {
+                    scheduleSyncOperation(
+                            new SyncOperation(info.service, info.userId,
+                                syncOperation.reason,
+                                syncOperation.syncSource, new Bundle(),
+                                0 /* delay */, 0 /* flex */,
+                                syncOperation.backoff, syncOperation.delayUntil));
+                }
             }
             // no need to schedule an alarm, as that will be done by our caller.
         }
@@ -2728,7 +2832,7 @@
             activeSyncContext.close();
             mActiveSyncContexts.remove(activeSyncContext);
             mSyncStorageEngine.removeActiveSync(activeSyncContext.mSyncInfo,
-                    activeSyncContext.mSyncOperation.userId);
+                    activeSyncContext.mSyncOperation.target.userId);
         }
 
         /**
@@ -2991,26 +3095,16 @@
         }
 
         public long insertStartSyncEvent(SyncOperation syncOperation) {
-            final int source = syncOperation.syncSource;
             final long now = System.currentTimeMillis();
-
-            EventLog.writeEvent(2720, syncOperation.authority,
-                                SyncStorageEngine.EVENT_START, source,
-                                syncOperation.account.name.hashCode());
-
-            return mSyncStorageEngine.insertStartSyncEvent(
-                    syncOperation.account, syncOperation.userId, syncOperation.reason,
-                    syncOperation.authority,
-                    now, source, syncOperation.isInitialization(), syncOperation.extras
-            );
+            EventLog.writeEvent(2720,
+                    syncOperation.toEventLog(SyncStorageEngine.EVENT_START));
+            return mSyncStorageEngine.insertStartSyncEvent(syncOperation, now);
         }
 
         public void stopSyncEvent(long rowId, SyncOperation syncOperation, String resultMessage,
                 int upstreamActivity, int downstreamActivity, long elapsedTime) {
-            EventLog.writeEvent(2720, syncOperation.authority,
-                                SyncStorageEngine.EVENT_STOP, syncOperation.syncSource,
-                                syncOperation.account.name.hashCode());
-
+            EventLog.writeEvent(2720,
+                    syncOperation.toEventLog(SyncStorageEngine.EVENT_STOP));
             mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime,
                     resultMessage, downstreamActivity, upstreamActivity);
         }
@@ -3025,6 +3119,79 @@
         return false;
     }
 
+    /**
+     * Sync extra comparison function.
+     * @param b1 bundle to compare
+     * @param b2 other bundle to compare
+     * @param includeSyncSettings if false, ignore system settings in bundle.
+     */
+    public static boolean syncExtrasEquals(Bundle b1, Bundle b2, boolean includeSyncSettings) {
+        if (b1 == b2) {
+            return true;
+        }
+        if (includeSyncSettings && b1.size() != b2.size()) {
+            return false;
+        }
+        for (String key : b1.keySet()) {
+            if (!includeSyncSettings && isSyncSetting(key)) {
+                continue;
+            }
+            if (!b2.containsKey(key)) {
+                return false;
+            }
+            if (!b1.get(key).equals(b2.get(key))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @return true if the provided key is used by the SyncManager in scheduling the sync.
+     */
+    private static boolean isSyncSetting(String key) {
+        if (key.equals(ContentResolver.SYNC_EXTRAS_EXPEDITED)) {
+            return true;
+        }
+        if (key.equals(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS)) {
+            return true;
+        }
+        if (key.equals(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF)) {
+            return true;
+        }
+        if (key.equals(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY)) {
+            return true;
+        }
+        if (key.equals(ContentResolver.SYNC_EXTRAS_MANUAL)) {
+            return true;
+        }
+        if (key.equals(ContentResolver.SYNC_EXTRAS_UPLOAD)) {
+            return true;
+        }
+        if (key.equals(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS)) {
+            return true;
+        }
+        if (key.equals(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS)) {
+            return true;
+        }
+        if (key.equals(ContentResolver.SYNC_EXTRAS_EXPECTED_UPLOAD)) {
+            return true;
+        }
+        if (key.equals(ContentResolver.SYNC_EXTRAS_EXPECTED_DOWNLOAD)) {
+            return true;
+        }
+        if (key.equals(ContentResolver.SYNC_EXTRAS_PRIORITY)) {
+            return true;
+        }
+        if (key.equals(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED)) {
+            return true;
+        }
+        if (key.equals(ContentResolver.SYNC_EXTRAS_INITIALIZE)) {
+            return true;
+        }
+        return false;
+    }
+
     static class PrintTable {
         private ArrayList<Object[]> mTable = Lists.newArrayList();
         private final int mCols;
diff --git a/services/java/com/android/server/content/SyncOperation.java b/services/java/com/android/server/content/SyncOperation.java
index ce1dde4..eef20b2 100644
--- a/services/java/com/android/server/content/SyncOperation.java
+++ b/services/java/com/android/server/content/SyncOperation.java
@@ -20,10 +20,9 @@
 import android.content.pm.PackageManager;
 import android.content.ComponentName;
 import android.content.ContentResolver;
-import android.content.SyncRequest;
 import android.os.Bundle;
 import android.os.SystemClock;
-import android.util.Pair;
+import android.util.Log;
 
 /**
  * Value type that represents a sync operation.
@@ -32,10 +31,13 @@
  * {@hide}
  */
 public class SyncOperation implements Comparable {
+    public static final String TAG = "SyncManager";
+
     public static final int REASON_BACKGROUND_DATA_SETTINGS_CHANGED = -1;
     public static final int REASON_ACCOUNTS_UPDATED = -2;
     public static final int REASON_SERVICE_CHANGED = -3;
     public static final int REASON_PERIODIC = -4;
+    /** Sync started because it has just been set to isSyncable. */
     public static final int REASON_IS_SYNCABLE = -5;
     /** Sync started because it has just been set to sync automatically. */
     public static final int REASON_SYNC_AUTO = -6;
@@ -54,19 +56,21 @@
             "UserStart",
     };
 
-    /** Account info to identify a SyncAdapter registered with the system. */
-    public final Account account;
-    /** Authority info to identify a SyncAdapter registered with the system. */
-    public final String authority;
-    /** Service to which this operation will bind to perform the sync. */
-    public final ComponentName service;
-    public final int userId;
+    public static final int SYNC_TARGET_UNKNOWN = 0;
+    public static final int SYNC_TARGET_ADAPTER = 1;
+    public static final int SYNC_TARGET_SERVICE = 2;
+
+    /** Identifying info for the authority for this operation. */
+    public final SyncStorageEngine.EndPoint target;
+    /** Why this sync was kicked off. {@link #REASON_NAMES} */
     public final int reason;
+    /** Where this sync was initiated. */
     public int syncSource;
     public final boolean allowParallelSyncs;
     public Bundle extras;
     public final String key;
     public boolean expedited;
+    /** Bare-bones version of this operation that is persisted across reboots. */
     public SyncStorageEngine.PendingOperation pendingOperation;
     /** Elapsed real time in millis at which to run this sync. */
     public long latestRunTime;
@@ -79,25 +83,56 @@
      * Depends on max(backoff, latestRunTime, and delayUntil).
      */
     public long effectiveRunTime;
-    /** Amount of time before {@link effectiveRunTime} from which this sync can run. */
+    /** Amount of time before {@link #effectiveRunTime} from which this sync can run. */
     public long flexTime;
 
-    public SyncOperation(Account account, int userId, int reason, int source, String authority,
+    public SyncOperation(Account account, int userId, int reason, int source, String provider,
             Bundle extras, long runTimeFromNow, long flexTime, long backoff,
             long delayUntil, boolean allowParallelSyncs) {
-        this.service = null;
-        this.account = account;
-        this.authority = authority;
-        this.userId = userId;
+        this.target = new SyncStorageEngine.EndPoint(account, provider, userId);
         this.reason = reason;
-        this.syncSource = source;
         this.allowParallelSyncs = allowParallelSyncs;
+        this.key = initialiseOperation(this.target, source, extras, runTimeFromNow, flexTime,
+                backoff, delayUntil);
+    }
+
+    public SyncOperation(ComponentName service, int userId, int reason, int source,
+            Bundle extras, long runTimeFromNow, long flexTime, long backoff,
+            long delayUntil) {
+        this.target = new SyncStorageEngine.EndPoint(service, userId);
+        // Default to true for sync service. The service itself decides how to handle this.
+        this.allowParallelSyncs = true;
+        this.reason = reason;
+        this.key =
+                initialiseOperation(this.target,
+                        source, extras, runTimeFromNow, flexTime, backoff, delayUntil);
+    }
+
+    /** Used to reschedule a sync at a new point in time. */
+    SyncOperation(SyncOperation other, long newRunTimeFromNow) {
+        this.target = other.target;
+        this.reason = other.reason;
+        this.expedited = other.expedited;
+        this.allowParallelSyncs = other.allowParallelSyncs;
+        // re-use old flex, but only 
+        long newFlexTime = Math.min(other.flexTime, newRunTimeFromNow);
+        this.key =
+                initialiseOperation(this.target,
+                    other.syncSource, other.extras,
+                    newRunTimeFromNow /* runTimeFromNow*/,
+                    newFlexTime /* flexTime */,
+                    other.backoff,
+                    0L /* delayUntil */);
+    }
+
+    private String initialiseOperation(SyncStorageEngine.EndPoint info, int source, Bundle extras,
+            long runTimeFromNow, long flexTime, long backoff, long delayUntil) {
+        this.syncSource = source;
         this.extras = new Bundle(extras);
         cleanBundle(this.extras);
         this.delayUntil = delayUntil;
         this.backoff = backoff;
         final long now = SystemClock.elapsedRealtime();
-        // Checks the extras bundle. Must occur after we set the internal bundle.
         if (runTimeFromNow < 0 || isExpedited()) {
             this.expedited = true;
             this.latestRunTime = now;
@@ -108,41 +143,11 @@
             this.flexTime = flexTime;
         }
         updateEffectiveRunTime();
-        this.key = toKey();
+        return toKey(info, extras);
     }
 
-    public SyncOperation(SyncRequest request, int userId, int reason, int source, long backoff,
-            long delayUntil, boolean allowParallelSyncs) {
-        if (request.hasAuthority()) {
-            Pair<Account, String> providerInfo = request.getProviderInfo();
-            this.account = providerInfo.first;
-            this.authority = providerInfo.second;
-            this.service = null;
-        } else {
-            this.service = request.getService();
-            this.account = null;
-            this.authority = null;
-        }
-        this.userId = userId;
-        this.reason = reason;
-        this.syncSource = source;
-        this.allowParallelSyncs = allowParallelSyncs;
-        this.extras = new Bundle(extras);
-        cleanBundle(this.extras);
-        this.delayUntil = delayUntil;
-        this.backoff = backoff;
-        final long now = SystemClock.elapsedRealtime();
-        if (request.isExpedited()) {
-            this.expedited = true;
-            this.latestRunTime = now;
-            this.flexTime = 0;
-        } else {
-            this.expedited = false;
-            this.latestRunTime = now + (request.getSyncRunTime() * 1000);
-            this.flexTime = request.getSyncFlexTime() * 1000;
-        }
-        updateEffectiveRunTime();
-        this.key = toKey();
+    public boolean matchesAuthority(SyncOperation other) {
+        return this.target.matches(other.target);
     }
 
     /**
@@ -159,11 +164,7 @@
         removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS);
         removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_EXPEDITED);
         removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS);
-        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_ALLOW_METERED);
-
-        // Remove Config data.
-        bundle.remove(ContentResolver.SYNC_EXTRAS_EXPECTED_UPLOAD);
-        bundle.remove(ContentResolver.SYNC_EXTRAS_EXPECTED_DOWNLOAD);
+        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DISALLOW_METERED);
     }
 
     private void removeFalseExtra(Bundle bundle, String extraName) {
@@ -172,22 +173,24 @@
         }
     }
 
-    /** Only used to immediately reschedule a sync. */
-    SyncOperation(SyncOperation other) {
-        this.service = other.service;
-        this.account = other.account;
-        this.authority = other.authority;
-        this.userId = other.userId;
-        this.reason = other.reason;
-        this.syncSource = other.syncSource;
-        this.extras = new Bundle(other.extras);
-        this.expedited = other.expedited;
-        this.latestRunTime = SystemClock.elapsedRealtime();
-        this.flexTime = 0L;
-        this.backoff = other.backoff;
-        this.allowParallelSyncs = other.allowParallelSyncs;
-        this.updateEffectiveRunTime();
-        this.key = toKey();
+    /**
+     * Determine whether if this sync operation is running, the provided operation would conflict
+     * with it.
+     * Parallel syncs allow multiple accounts to be synced at the same time. 
+     */
+    public boolean isConflict(SyncOperation toRun) {
+        final SyncStorageEngine.EndPoint other = toRun.target;
+        if (target.target_provider) {
+            return target.account.type.equals(other.account.type)
+                    && target.provider.equals(other.provider)
+                    && target.userId == other.userId
+                    && (!allowParallelSyncs
+                            || target.account.name.equals(other.account.name));
+        } else {
+            // Ops that target a service default to allow parallel syncs, which is handled by the
+            // service returning SYNC_IN_PROGRESS if they don't.
+            return target.service.equals(other.service) && !allowParallelSyncs;
+        }
     }
 
     @Override
@@ -196,18 +199,26 @@
     }
 
     public String dump(PackageManager pm, boolean useOneLine) {
-        StringBuilder sb = new StringBuilder()
-                .append(account.name)
+        StringBuilder sb = new StringBuilder();
+        if (target.target_provider) {
+            sb.append(target.account.name)
                 .append(" u")
-                .append(userId).append(" (")
-                .append(account.type)
+                .append(target.userId).append(" (")
+                .append(target.account.type)
                 .append(")")
                 .append(", ")
-                .append(authority)
-                .append(", ")
-                .append(SyncStorageEngine.SOURCES[syncSource])
-                .append(", latestRunTime ")
-                .append(latestRunTime);
+                .append(target.provider)
+                .append(", ");
+        } else if (target.target_service) {
+            sb.append(target.service.getPackageName())
+                .append(" u")
+                .append(target.userId).append(" (")
+                .append(target.service.getClassName()).append(")")
+                .append(", ");
+        }
+        sb.append(SyncStorageEngine.SOURCES[syncSource])
+            .append(", currentRunTime ")
+            .append(effectiveRunTime);
         if (expedited) {
             sb.append(", EXPEDITED");
         }
@@ -245,10 +256,6 @@
         }
     }
 
-    public boolean isMetered() {
-        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_ALLOW_METERED, false);
-    }
-
     public boolean isInitialization() {
         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
     }
@@ -261,28 +268,39 @@
         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
     }
 
+    public boolean isNotAllowedOnMetered() {
+        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, false);
+    }
+
     /** Changed in V3. */
-    private String toKey() {
+    public static String toKey(SyncStorageEngine.EndPoint info, Bundle extras) {
         StringBuilder sb = new StringBuilder();
-        if (service == null) {
-            sb.append("authority: ").append(authority);
-            sb.append(" account {name=" + account.name + ", user=" + userId + ", type=" + account.type
+        if (info.target_provider) {
+            sb.append("provider: ").append(info.provider);
+            sb.append(" account {name=" + info.account.name
+                    + ", user="
+                    + info.userId
+                    + ", type="
+                    + info.account.type
                     + "}");
-        } else {
+        } else if (info.target_service) {
             sb.append("service {package=" )
-                .append(service.getPackageName())
+                .append(info.service.getPackageName())
                 .append(" user=")
-                .append(userId)
+                .append(info.userId)
                 .append(", class=")
-                .append(service.getClassName())
+                .append(info.service.getClassName())
                 .append("}");
+        } else {
+            Log.v(TAG, "Converting SyncOperaton to key, invalid target: " + info.toString());
+            return "";
         }
         sb.append(" extras: ");
         extrasToStringBuilder(extras, sb);
         return sb.toString();
     }
 
-    public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
+    private static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
         sb.append("[");
         for (String key : bundle.keySet()) {
             sb.append(key).append("=").append(bundle.get(key)).append(" ");
@@ -290,6 +308,31 @@
         sb.append("]");
     }
 
+    public String wakeLockKey() {
+        if (target.target_provider) {
+            return target.account.name + "/" + target.account.type + ":" + target.provider;
+        } else if (target.target_service) {
+            return target.service.getPackageName() + "/" + target.service.getClassName();
+        } else {
+            Log.wtf(TAG, "Invalid target getting wakelock for operation - " + key);
+            return null;
+        }
+    }
+
+    public String wakeLockName() {
+        if (target.target_provider) {
+            return "/" + target.provider
+                    + "/" + target.account.type
+                    + "/" + target.account.name;
+        } else if (target.target_service) {
+            return "/" + target.service.getPackageName()
+                    + "/" + target.service.getClassName();
+        } else {
+            Log.wtf(TAG, "Invalid target getting wakelock name for operation - " + key);
+            return null;
+        }
+    }
+
     /**
      * Update the effective run time of this Operation based on latestRunTime (specified at
      * creation time of sync), delayUntil (specified by SyncAdapter), or backoff (specified by
@@ -325,4 +368,21 @@
             return 0;
         }
     }
+
+    // TODO: Test this to make sure that casting to object doesn't lose the type info for EventLog.
+    public Object[] toEventLog(int event) {
+        Object[] logArray = new Object[4];
+        logArray[1] = event;
+        logArray[2] = syncSource;
+        if (target.target_provider) {
+            logArray[0] = target.provider;
+            logArray[3] = target.account.name.hashCode();
+        } else if (target.target_service) {
+            logArray[0] = target.service.getPackageName();
+            logArray[3] = target.service.hashCode();
+        } else {
+            Log.wtf(TAG, "sync op with invalid target: " + key);
+        }
+        return logArray;
+    }
 }
diff --git a/services/java/com/android/server/content/SyncQueue.java b/services/java/com/android/server/content/SyncQueue.java
index 6f3fe6e..da7efba 100644
--- a/services/java/com/android/server/content/SyncQueue.java
+++ b/services/java/com/android/server/content/SyncQueue.java
@@ -16,12 +16,11 @@
 
 package com.android.server.content;
 
-import android.accounts.Account;
 import android.content.pm.PackageManager;
-import android.content.pm.RegisteredServicesCache;
 import android.content.SyncAdapterType;
 import android.content.SyncAdaptersCache;
 import android.content.pm.RegisteredServicesCache.ServiceInfo;
+import android.os.Bundle;
 import android.os.SystemClock;
 import android.text.format.DateUtils;
 import android.util.Log;
@@ -60,25 +59,51 @@
 
     public void addPendingOperations(int userId) {
         for (SyncStorageEngine.PendingOperation op : mSyncStorageEngine.getPendingOperations()) {
-            if (op.userId != userId) continue;
+            final SyncStorageEngine.EndPoint info = op.authority;
+            if (info.userId != userId) continue;
 
-            final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(
-                    op.account, op.userId, op.authority);
-            final ServiceInfo<SyncAdapterType> syncAdapterInfo = mSyncAdapters.getServiceInfo(
-                    SyncAdapterType.newKey(op.authority, op.account.type), op.userId);
-            if (syncAdapterInfo == null) {
-                Log.w(TAG, "Missing sync adapter info for authority " + op.authority + ", userId "
-                        + op.userId);
-                continue;
+            final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(info);
+            SyncOperation operationToAdd;
+            if (info.target_provider) {
+                final ServiceInfo<SyncAdapterType> syncAdapterInfo = mSyncAdapters.getServiceInfo(
+                        SyncAdapterType.newKey(info.provider, info.account.type), info.userId);
+                if (syncAdapterInfo == null) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.w(TAG, "Missing sync adapter info for authority " + op.authority);
+                    }
+                    continue;
+                }
+                operationToAdd = new SyncOperation(
+                        info.account, info.userId, op.reason, op.syncSource, info.provider,
+                        op.extras,
+                        0 /* delay */,
+                        0 /* flex */,
+                        backoff != null ? backoff.first : 0,
+                        mSyncStorageEngine.getDelayUntilTime(info),
+                        syncAdapterInfo.type.allowParallelSyncs());
+                operationToAdd.expedited = op.expedited;
+                operationToAdd.pendingOperation = op;
+                add(operationToAdd, op);
+            } else if (info.target_service) {
+                try {
+                    mPackageManager.getServiceInfo(info.service, 0);
+                } catch (PackageManager.NameNotFoundException e) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.w(TAG, "Missing sync servce for authority " + op.authority);
+                    }
+                    continue;
+                }
+                operationToAdd = new SyncOperation(
+                        info.service, info.userId, op.reason, op.syncSource,
+                        op.extras,
+                        0 /* delay */,
+                        0 /* flex */,
+                        backoff != null ? backoff.first : 0,
+                        mSyncStorageEngine.getDelayUntilTime(info));
+                operationToAdd.expedited = op.expedited;
+                operationToAdd.pendingOperation = op;
+                add(operationToAdd, op);
             }
-            SyncOperation syncOperation = new SyncOperation(
-                    op.account, op.userId, op.reason, op.syncSource, op.authority, op.extras,
-                    0 /* delay */, 0 /* flex */, backoff != null ? backoff.first : 0,
-                    mSyncStorageEngine.getDelayUntilTime(op.account, op.userId, op.authority),
-                    syncAdapterInfo.type.allowParallelSyncs());
-            syncOperation.expedited = op.expedited;
-            syncOperation.pendingOperation = op;
-            add(syncOperation, op);
         }
     }
 
@@ -119,12 +144,8 @@
         operation.pendingOperation = pop;
         // Don't update the PendingOp if one already exists. This really is just a placeholder,
         // no actual scheduling info is placed here.
-        // TODO: Change this to support service components.
         if (operation.pendingOperation == null) {
-            pop = new SyncStorageEngine.PendingOperation(
-                    operation.account, operation.userId, operation.reason, operation.syncSource,
-                    operation.authority, operation.extras, operation.expedited);
-            pop = mSyncStorageEngine.insertIntoPending(pop);
+            pop = mSyncStorageEngine.insertIntoPending(operation);
             if (pop == null) {
                 throw new IllegalStateException("error adding pending sync operation "
                         + operation);
@@ -136,17 +157,16 @@
         return true;
     }
 
-    public void removeUser(int userId) {
+    public void removeUserLocked(int userId) {
         ArrayList<SyncOperation> opsToRemove = new ArrayList<SyncOperation>();
         for (SyncOperation op : mOperationsMap.values()) {
-            if (op.userId == userId) {
+            if (op.target.userId == userId) {
                 opsToRemove.add(op);
             }
         }
-
-        for (SyncOperation op : opsToRemove) {
-            remove(op);
-        }
+            for (SyncOperation op : opsToRemove) {
+                remove(op);
+            }
     }
 
     /**
@@ -154,8 +174,15 @@
      * @param operation the operation to remove
      */
     public void remove(SyncOperation operation) {
+        boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
         SyncOperation operationToRemove = mOperationsMap.remove(operation.key);
+        if (isLoggable) {
+            Log.d(TAG, "Attempting to remove: " + operation.key);
+        }
         if (operationToRemove == null) {
+            if (isLoggable) {
+                Log.d(TAG, "Could not find: " + operation.key);
+            }
             return;
         }
         if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) {
@@ -164,41 +191,58 @@
         }
     }
 
-    public void onBackoffChanged(Account account, int userId, String providerName, long backoff) {
-        // for each op that matches the account and provider update its
+    /** Reset backoffs for all operations in the queue. */
+    public void clearBackoffs() {
+        for (SyncOperation op : mOperationsMap.values()) {
+            op.backoff = 0L;
+            op.updateEffectiveRunTime();
+        }
+    }
+
+    public void onBackoffChanged(SyncStorageEngine.EndPoint target, long backoff) {
+        // For each op that matches the authority of the changed op, update its
         // backoff and effectiveStartTime
         for (SyncOperation op : mOperationsMap.values()) {
-            if (op.account.equals(account) && op.authority.equals(providerName)
-                    && op.userId == userId) {
+            if (op.target.matches(target)) {
                 op.backoff = backoff;
                 op.updateEffectiveRunTime();
             }
         }
     }
 
-    public void onDelayUntilTimeChanged(Account account, String providerName, long delayUntil) {
-        // for each op that matches the account and provider update its
-        // delayUntilTime and effectiveStartTime
+    public void onDelayUntilTimeChanged(SyncStorageEngine.EndPoint target, long delayUntil) {
+        // for each op that matches the authority info of the provided op, change the delay time.
         for (SyncOperation op : mOperationsMap.values()) {
-            if (op.account.equals(account) && op.authority.equals(providerName)) {
+            if (op.target.matches(target)) {
                 op.delayUntil = delayUntil;
                 op.updateEffectiveRunTime();
             }
         }
     }
 
-    public void remove(Account account, int userId, String authority) {
+    /**
+     * Remove all of the SyncOperations associated with a given target.
+     *
+     * @param info target object provided here can have null Account/provider. This is the case
+     * where you want to remove all ops associated with a provider (null Account) or all ops
+     * associated with an account (null provider).
+     * @param extras option bundle to include to further specify which operation to remove. If this
+     * bundle contains sync settings flags, they are ignored.
+     */
+    public void remove(final SyncStorageEngine.EndPoint info, Bundle extras) {
         Iterator<Map.Entry<String, SyncOperation>> entries = mOperationsMap.entrySet().iterator();
         while (entries.hasNext()) {
             Map.Entry<String, SyncOperation> entry = entries.next();
             SyncOperation syncOperation = entry.getValue();
-            if (account != null && !syncOperation.account.equals(account)) {
+            final SyncStorageEngine.EndPoint opInfo = syncOperation.target;
+            if (!opInfo.matches(info)) {
                 continue;
             }
-            if (authority != null && !syncOperation.authority.equals(authority)) {
-                continue;
-            }
-            if (userId != syncOperation.userId) {
+            if (extras != null
+                    && !SyncManager.syncExtrasEquals(
+                        syncOperation.extras,
+                        extras,
+                        false /* no config flags*/)) {
                 continue;
             }
             entries.remove();
diff --git a/services/java/com/android/server/content/SyncStorageEngine.java b/services/java/com/android/server/content/SyncStorageEngine.java
index 1b9ed98..6cc3d8a 100644
--- a/services/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/java/com/android/server/content/SyncStorageEngine.java
@@ -24,6 +24,7 @@
 import android.content.ISyncStatusObserver;
 import android.content.PeriodicSync;
 import android.content.SyncInfo;
+import android.content.SyncRequest;
 import android.content.SyncStatusInfo;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
@@ -36,10 +37,12 @@
 import android.os.Parcel;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.AtomicFile;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
+import android.util.ArrayMap;
 import android.util.Xml;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -71,7 +74,6 @@
 public class SyncStorageEngine extends Handler {
 
     private static final String TAG = "SyncManager";
-    private static final boolean DEBUG = false;
     private static final String TAG_FILE = "SyncManagerFile";
 
     private static final String XML_ATTR_NEXT_AUTHORITY_ID = "nextAuthorityId";
@@ -108,10 +110,7 @@
 
     /** Enum value for a local-initiated sync. */
     public static final int SOURCE_LOCAL = 1;
-    /**
-     * Enum value for a poll-based sync (e.g., upon connection to
-     * network)
-     */
+    /** Enum value for a poll-based sync (e.g., upon connection to network) */
     public static final int SOURCE_POLL = 2;
 
     /** Enum value for a user-initiated sync. */
@@ -119,6 +118,9 @@
 
     /** Enum value for a periodic sync. */
     public static final int SOURCE_PERIODIC = 4;
+    
+    /** Enum value for a sync started for a service. */
+    public static final int SOURCE_SERVICE = 5;
 
     public static final long NOT_IN_BACKOFF_MODE = -1;
 
@@ -128,7 +130,8 @@
                                              "LOCAL",
                                              "POLL",
                                              "USER",
-                                             "PERIODIC" };
+                                             "PERIODIC",
+                                             "SERVICE"};
 
     // The MESG column will contain one of these or one of the Error types.
     public static final String MESG_SUCCESS = "success";
@@ -156,41 +159,53 @@
     }
 
     public static class PendingOperation {
-        final Account account;
-        final int userId;
+        final EndPoint authority;
         final int reason;
         final int syncSource;
-        final String authority;
         final Bundle extras;        // note: read-only.
-        final ComponentName serviceName;
         final boolean expedited;
 
-        int authorityId;
+        final int authorityId;
+        // No longer used.
+        // Keep around for sake up updating from pending.bin to pending.xml
         byte[] flatExtras;
 
-        PendingOperation(Account account, int userId, int reason, int source,
-                String authority, Bundle extras, boolean expedited) {
-            this.account = account;
-            this.userId = userId;
+        PendingOperation(AuthorityInfo authority, int reason, int source,
+                 Bundle extras, boolean expedited) {
+            this.authority = authority.base;
             this.syncSource = source;
             this.reason = reason;
-            this.authority = authority;
             this.extras = extras != null ? new Bundle(extras) : extras;
             this.expedited = expedited;
-            this.authorityId = -1;
-            this.serviceName = null;
+            this.authorityId = authority.ident;
         }
 
         PendingOperation(PendingOperation other) {
-            this.account = other.account;
-            this.userId = other.userId;
             this.reason = other.reason;
             this.syncSource = other.syncSource;
             this.authority = other.authority;
             this.extras = other.extras;
             this.authorityId = other.authorityId;
             this.expedited = other.expedited;
-            this.serviceName = other.serviceName;
+        }
+
+        /**
+         * Considered equal if they target the same sync adapter (A {@link android.content.SyncService}
+         * is considered an adapter), for the same userId.
+         * @param other PendingOperation to compare.
+         * @return true if the two pending ops are the same.
+         */
+        public boolean equals(PendingOperation other) {
+            return authority.matches(other.authority);
+        }
+
+        public String toString() {
+            return "service=" + authority.service
+                        + " user=" + authority.userId
+                        + " auth=" + authority
+                        + " account=" + authority.account
+                        + " src=" + syncSource
+                        + " extras=" + extras;
         }
     }
 
@@ -204,17 +219,93 @@
         }
     }
 
-    public static class AuthorityInfo {
+    /**  Bare bones representation of a sync target. */
+    public static class EndPoint {
+        public final static EndPoint USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL =
+                new EndPoint(null, null, UserHandle.USER_ALL);
         final ComponentName service;
         final Account account;
         final int userId;
-        final String authority;
+        final String provider;
+        final boolean target_service;
+        final boolean target_provider;
+
+        public EndPoint(ComponentName service, int userId) {
+            this.service = service;
+            this.userId = userId;
+            this.account = null;
+            this.provider = null;
+            this.target_service = true;
+            this.target_provider = false;
+        }
+
+        public EndPoint(Account account, String provider, int userId) {
+            this.account = account;
+            this.provider = provider;
+            this.userId = userId;
+            this.service = null;
+            this.target_service = false;
+            this.target_provider = true;
+        }
+
+        /**
+         * An Authority for a sync matches if it targets the same sync adapter for the same user.
+         * If the authority is for a provider/account pair and the account or provider is null, it
+         * matches by default.
+         */
+        public boolean matches(EndPoint other) {
+            if (userId != other.userId
+                    && userId != UserHandle.USER_ALL
+                    && other.userId != UserHandle.USER_ALL) {
+                return false;
+            }
+            if (target_service && other.target_service) {
+                return service.equals(other.service);
+            } else if (target_provider && other.target_provider) {
+                boolean accountsMatch;
+                if (account == null || other.account == null) {
+                    accountsMatch = true;
+                } else {
+                    accountsMatch = account.equals(other.account);
+                }
+                boolean providersMatch;
+                if (provider == null || other.provider == null) {
+                    providersMatch = true;
+                } else {
+                    providersMatch = provider.equals(other.provider);
+                }
+                return accountsMatch && providersMatch;
+            }
+            return false;
+        }
+
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            if (target_provider) {
+                sb.append(account == null ? "ALL ACCS" : account.name)
+                    .append("/")
+                    .append(provider == null ? "ALL PDRS" : provider);
+            } else if (target_service) {
+                sb.append(service.getPackageName());
+            } else {
+                sb.append("invalid target");
+            }
+            return sb.toString();
+        }
+    }
+
+    public static class AuthorityInfo {
+        final EndPoint base;
         final int ident;
         boolean enabled;
         int syncable;
+        /** Time at which this sync will run, taking into account backoff. */
         long backoffTime;
+        /** Amount of delay due to backoff. */
         long backoffDelay;
+        /** Offset to add to any requests coming to this authority. */
         long delayUntil;
+
         final ArrayList<PeriodicSync> periodicSyncs;
 
         /**
@@ -224,10 +315,7 @@
          * @param toCopy AuthorityInfo to be copied.
          */
         AuthorityInfo(AuthorityInfo toCopy) {
-            account = toCopy.account;
-            userId = toCopy.userId;
-            authority = toCopy.authority;
-            service = toCopy.service;
+            base = toCopy.base;
             ident = toCopy.ident;
             enabled = toCopy.enabled;
             syncable = toCopy.syncable;
@@ -241,56 +329,42 @@
             }
         }
 
-        /**
-         * Create an authority with one periodic sync scheduled with an empty bundle and syncing
-         * every day. An empty bundle is considered equal to any other bundle see
-         * {@link PeriodicSync.syncExtrasEquals}.
-         * @param account Account that this authority syncs.
-         * @param userId which user this sync is registered for.
-         * @param userId user for which this authority is registered.
-         * @param ident id of this authority.
-         */
-        AuthorityInfo(Account account, int userId, String authority, int ident) {
-            this.account = account;
-            this.userId = userId;
-            this.authority = authority;
-            this.service = null;
-            this.ident = ident;
-            enabled = SYNC_ENABLED_DEFAULT;
+        AuthorityInfo(EndPoint info, int id) {
+            base = info;
+            ident = id;
+            enabled = info.target_provider ?
+                    SYNC_ENABLED_DEFAULT : true;
+            // Service is active by default,
+            if (info.target_service) {
+                this.syncable = 1;
+            }
+            periodicSyncs = new ArrayList<PeriodicSync>();
+            defaultInitialisation();
+        }
+
+        private void defaultInitialisation() {
             syncable = -1; // default to "unknown"
             backoffTime = -1; // if < 0 then we aren't in backoff mode
             backoffDelay = -1; // if < 0 then we aren't in backoff mode
-            periodicSyncs = new ArrayList<PeriodicSync>();
-            // Old version adds one periodic sync a day.
-            periodicSyncs.add(new PeriodicSync(account, authority,
-                                new Bundle(),
-                                DEFAULT_POLL_FREQUENCY_SECONDS,
-                                calculateDefaultFlexTime(DEFAULT_POLL_FREQUENCY_SECONDS)));
+            PeriodicSync defaultSync;
+            // Old version is one sync a day. Empty bundle gets replaced by any addPeriodicSync()
+            // call.
+            if (base.target_provider) {
+                defaultSync =
+                        new PeriodicSync(base.account, base.provider,
+                            new Bundle(),
+                            DEFAULT_POLL_FREQUENCY_SECONDS,
+                            calculateDefaultFlexTime(DEFAULT_POLL_FREQUENCY_SECONDS));
+                periodicSyncs.add(defaultSync);
+            }
+            
         }
 
         /**
-         * Create an authority with one periodic sync scheduled with an empty bundle and syncing
-         * every day using a sync service.
-         * @param cname sync service identifier.
-         * @param userId user for which this authority is registered.
-         * @param ident id of this authority.
+         * Two AuthorityInfos are considered equal if they have the same authority.
          */
-        AuthorityInfo(ComponentName cname, int userId, int ident) {
-            this.account = null;
-            this.userId = userId;
-            this.authority = null;
-            this.service = cname;
-            this.ident = ident;
-            // Sync service is always enabled.
-            enabled = true;
-            syncable = -1; // default to "unknown"
-            backoffTime = -1; // if < 0 then we aren't in backoff mode
-            backoffDelay = -1; // if < 0 then we aren't in backoff mode
-            periodicSyncs = new ArrayList<PeriodicSync>();
-            periodicSyncs.add(new PeriodicSync(account, authority,
-                                new Bundle(),
-                                DEFAULT_POLL_FREQUENCY_SECONDS,
-                                calculateDefaultFlexTime(DEFAULT_POLL_FREQUENCY_SECONDS)));
+        public boolean equals(EndPoint other) {
+            return base.matches(other);
         }
     }
 
@@ -322,16 +396,9 @@
     }
 
     interface OnSyncRequestListener {
-        /**
-         * Called when a sync is needed on an account(s) due to some change in state.
-         * @param account
-         * @param userId
-         * @param reason
-         * @param authority
-         * @param extras
-         */
-        public void onSyncRequest(Account account, int userId, int reason, String authority,
-                Bundle extras);
+
+        /** Called when a sync is needed on an account(s) due to some change in state. */
+        public void onSyncRequest(EndPoint info, int reason, Bundle extras);
     }
 
     // Primary list of all syncable authorities.  Also our global lock.
@@ -357,8 +424,8 @@
             = new RemoteCallbackList<ISyncStatusObserver>();
 
     /** Reverse mapping for component name -> <userid -> authority id>. */
-    private final HashMap<ComponentName, SparseArray<AuthorityInfo>> mServices =
-            new HashMap<ComponentName, SparseArray<AuthorityInfo>>();
+    private final ArrayMap<ComponentName, SparseArray<AuthorityInfo>> mServices =
+            new ArrayMap<ComponentName, SparseArray<AuthorityInfo>>();
 
     private int mNextAuthorityId = 0;
 
@@ -501,7 +568,7 @@
      * @return amount of seconds before syncTimeSeconds that the sync can occur.
      *      I.e.
      *      earliest_sync_time = syncTimeSeconds - calculateDefaultFlexTime(syncTimeSeconds)
-     * The flex time is capped at a percentage of the {@link DEFAULT_POLL_FREQUENCY_SECONDS}.
+     * The flex time is capped at a percentage of the {@link #DEFAULT_POLL_FREQUENCY_SECONDS}.
      */
     public static long calculateDefaultFlexTime(long syncTimeSeconds) {
         if (syncTimeSeconds < DEFAULT_MIN_FLEX_ALLOWED_SECS) {
@@ -535,7 +602,7 @@
             mChangeListeners.finishBroadcast();
         }
 
-        if (DEBUG) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "reportChange " + which + " to: " + reports);
         }
 
@@ -555,7 +622,8 @@
     public boolean getSyncAutomatically(Account account, int userId, String providerName) {
         synchronized (mAuthorities) {
             if (account != null) {
-                AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
+                AuthorityInfo authority = getAuthorityLocked(
+                        new EndPoint(account, providerName, userId),
                         "getSyncAutomatically");
                 return authority != null && authority.enabled;
             }
@@ -563,10 +631,9 @@
             int i = mAuthorities.size();
             while (i > 0) {
                 i--;
-                AuthorityInfo authority = mAuthorities.valueAt(i);
-                if (authority.authority.equals(providerName)
-                        && authority.userId == userId
-                        && authority.enabled) {
+                AuthorityInfo authorityInfo = mAuthorities.valueAt(i);
+                if (authorityInfo.base.matches(new EndPoint(account, providerName, userId))
+                        && authorityInfo.enabled) {
                     return true;
                 }
             }
@@ -576,15 +643,18 @@
 
     public void setSyncAutomatically(Account account, int userId, String providerName,
             boolean sync) {
-        if (DEBUG) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.d(TAG, "setSyncAutomatically: " + /* account + */" provider " + providerName
                     + ", user " + userId + " -> " + sync);
         }
         synchronized (mAuthorities) {
-            AuthorityInfo authority = getOrCreateAuthorityLocked(account, userId, providerName, -1,
-                    false);
+            AuthorityInfo authority =
+                    getOrCreateAuthorityLocked(
+                            new EndPoint(account, providerName, userId),
+                            -1 /* ident */,
+                            false);
             if (authority.enabled == sync) {
-                if (DEBUG) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                     Log.d(TAG, "setSyncAutomatically: already set to " + sync + ", doing nothing");
                 }
                 return;
@@ -603,8 +673,9 @@
     public int getIsSyncable(Account account, int userId, String providerName) {
         synchronized (mAuthorities) {
             if (account != null) {
-                AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
-                        "getIsSyncable");
+                AuthorityInfo authority = getAuthorityLocked(
+                        new EndPoint(account, providerName, userId),
+                        "get authority syncable");
                 if (authority == null) {
                     return -1;
                 }
@@ -614,9 +685,10 @@
             int i = mAuthorities.size();
             while (i > 0) {
                 i--;
-                AuthorityInfo authority = mAuthorities.valueAt(i);
-                if (authority.authority.equals(providerName)) {
-                    return authority.syncable;
+                AuthorityInfo authorityInfo = mAuthorities.valueAt(i);
+                if (authorityInfo.base != null
+                        && authorityInfo.base.provider.equals(providerName)) {
+                    return authorityInfo.syncable;
                 }
             }
             return -1;
@@ -624,103 +696,163 @@
     }
 
     public void setIsSyncable(Account account, int userId, String providerName, int syncable) {
+        synchronized (mAuthorities) {
+            AuthorityInfo authority =
+                    getOrCreateAuthorityLocked(
+                            new EndPoint(account, providerName, userId),
+                            -1 /* ident */,
+                            false);
+            setSyncableLocked(authority, syncable);
+        }
+    }
+
+    public int getIsTargetServiceActive(ComponentName cname, int userId) {
+        synchronized (mAuthorities) {
+            if (cname != null) {
+                AuthorityInfo authority = getAuthorityLocked(
+                        new EndPoint(cname, userId),
+                        "get service enabled");
+                if (authority == null) {
+                    return -1;
+                }
+                return authority.syncable;
+            }
+            return -1;
+        }
+    }
+
+    public void setIsEnabled(ComponentName cname, int userId, int syncable) {
+        synchronized (mAuthorities) {
+            AuthorityInfo authority =
+                    getOrCreateAuthorityLocked(
+                            new EndPoint(cname, userId), -1, false);
+            setSyncableLocked(authority, syncable);
+        }
+    }
+
+    /**
+     * An enabled sync service and a syncable provider's adapter both get resolved to the same
+     * persisted variable - namely the "syncable" attribute for an AuthorityInfo in accounts.xml.
+     * @param aInfo
+     * @param syncable
+     */
+    private void setSyncableLocked(AuthorityInfo aInfo, int syncable) {
         if (syncable > 1) {
             syncable = 1;
         } else if (syncable < -1) {
             syncable = -1;
         }
-        if (DEBUG) {
-            Log.d(TAG, "setIsSyncable: " + account + ", provider " + providerName
-                    + ", user " + userId + " -> " + syncable);
-        }
-        synchronized (mAuthorities) {
-            AuthorityInfo authority =
-                    getOrCreateAuthorityLocked(account, userId, providerName, -1, false);
-            if (authority.syncable == syncable) {
-                if (DEBUG) {
-                    Log.d(TAG, "setIsSyncable: already set to " + syncable + ", doing nothing");
-                }
-                return;
-            }
-            authority.syncable = syncable;
-            writeAccountInfoLocked();
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.d(TAG, "setIsSyncable: " + aInfo.toString() + " -> " + syncable);
         }
 
+        if (aInfo.syncable == syncable) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.d(TAG, "setIsSyncable: already set to " + syncable + ", doing nothing");
+            }
+            return;
+        }
+        aInfo.syncable = syncable;
+        writeAccountInfoLocked();
+
         if (syncable > 0) {
-            requestSync(account, userId, SyncOperation.REASON_IS_SYNCABLE,  providerName,
-                    new Bundle());
+            requestSync(aInfo, SyncOperation.REASON_IS_SYNCABLE, new Bundle());
         }
         reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
     }
 
-    public Pair<Long, Long> getBackoff(Account account, int userId, String providerName) {
+    public Pair<Long, Long> getBackoff(EndPoint info) {
         synchronized (mAuthorities) {
-            AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
-                    "getBackoff");
-            if (authority == null || authority.backoffTime < 0) {
-                return null;
+            AuthorityInfo authority = getAuthorityLocked(info, "getBackoff");
+            if (authority != null) {
+                return Pair.create(authority.backoffTime, authority.backoffDelay);
             }
-            return Pair.create(authority.backoffTime, authority.backoffDelay);
+            return null;
         }
     }
 
-    public void setBackoff(Account account, int userId, String providerName,
-            long nextSyncTime, long nextDelay) {
-        if (DEBUG) {
-            Log.v(TAG, "setBackoff: " + account + ", provider " + providerName
-                    + ", user " + userId
+    /**
+     * Update the backoff for the given endpoint. The endpoint may be for a provider/account and
+     * the account or provider info be null, which signifies all accounts or providers.
+     */
+    public void setBackoff(EndPoint info, long nextSyncTime, long nextDelay) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "setBackoff: " + info
                     + " -> nextSyncTime " + nextSyncTime + ", nextDelay " + nextDelay);
         }
-        boolean changed = false;
+        boolean changed;
         synchronized (mAuthorities) {
-            if (account == null || providerName == null) {
-                for (AccountInfo accountInfo : mAccounts.values()) {
-                    if (account != null && !account.equals(accountInfo.accountAndUser.account)
-                            && userId != accountInfo.accountAndUser.userId) {
-                        continue;
-                    }
-                    for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) {
-                        if (providerName != null
-                                && !providerName.equals(authorityInfo.authority)) {
-                            continue;
-                        }
-                        if (authorityInfo.backoffTime != nextSyncTime
-                                || authorityInfo.backoffDelay != nextDelay) {
-                            authorityInfo.backoffTime = nextSyncTime;
-                            authorityInfo.backoffDelay = nextDelay;
-                            changed = true;
-                        }
-                    }
-                }
+            if (info.target_provider
+                    && (info.account == null || info.provider == null)) {
+                // Do more work for a provider sync if the provided info has specified all
+                // accounts/providers. 
+                changed = setBackoffLocked(
+                        info.account /* may be null */,
+                        info.userId,
+                        info.provider /* may be null */,
+                        nextSyncTime, nextDelay);
             } else {
-                AuthorityInfo authority =
-                        getOrCreateAuthorityLocked(account, userId, providerName, -1 /* ident */,
-                                true);
-                if (authority.backoffTime == nextSyncTime && authority.backoffDelay == nextDelay) {
-                    return;
+                AuthorityInfo authorityInfo =
+                        getOrCreateAuthorityLocked(info, -1 /* ident */, true);
+                if (authorityInfo.backoffTime == nextSyncTime
+                        && authorityInfo.backoffDelay == nextDelay) {
+                    changed = false;
+                } else {
+                    authorityInfo.backoffTime = nextSyncTime;
+                    authorityInfo.backoffDelay = nextDelay;
+                    changed = true;
                 }
-                authority.backoffTime = nextSyncTime;
-                authority.backoffDelay = nextDelay;
-                changed = true;
             }
         }
-
         if (changed) {
             reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
         }
     }
 
+    /**
+     * Either set backoff for a specific authority, or set backoff for all the
+     * accounts on a specific adapter/all adapters.
+     *
+     * @param account account for which to set backoff. Null to specify all accounts.
+     * @param userId id of the user making this request.
+     * @param providerName provider for which to set backoff. Null to specify all providers.
+     */
+    private boolean setBackoffLocked(Account account, int userId, String providerName,
+            long nextSyncTime, long nextDelay) {
+        boolean changed = false;
+        for (AccountInfo accountInfo : mAccounts.values()) {
+            if (account != null && !account.equals(accountInfo.accountAndUser.account)
+                    && userId != accountInfo.accountAndUser.userId) {
+                continue;
+            }
+            for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) {
+                if (providerName != null
+                        && !providerName.equals(authorityInfo.base.provider)) {
+                    continue;
+                }
+                if (authorityInfo.backoffTime != nextSyncTime
+                        || authorityInfo.backoffDelay != nextDelay) {
+                    authorityInfo.backoffTime = nextSyncTime;
+                    authorityInfo.backoffDelay = nextDelay;
+                    changed = true;
+                }
+            }
+        }
+        return changed;
+    }
+
     public void clearAllBackoffs(SyncQueue syncQueue) {
         boolean changed = false;
         synchronized (mAuthorities) {
             synchronized (syncQueue) {
+                // Clear backoff for all sync adapters.
                 for (AccountInfo accountInfo : mAccounts.values()) {
                     for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) {
                         if (authorityInfo.backoffTime != NOT_IN_BACKOFF_MODE
                                 || authorityInfo.backoffDelay != NOT_IN_BACKOFF_MODE) {
-                            if (DEBUG) {
+                            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                                 Log.v(TAG, "clearAllBackoffs:"
-                                        + " authority:" + authorityInfo.authority
+                                        + " authority:" + authorityInfo.base
                                         + " account:" + accountInfo.accountAndUser.account.name
                                         + " user:" + accountInfo.accountAndUser.userId
                                         + " backoffTime was: " + authorityInfo.backoffTime
@@ -728,12 +860,23 @@
                             }
                             authorityInfo.backoffTime = NOT_IN_BACKOFF_MODE;
                             authorityInfo.backoffDelay = NOT_IN_BACKOFF_MODE;
-                            syncQueue.onBackoffChanged(accountInfo.accountAndUser.account,
-                                    accountInfo.accountAndUser.userId, authorityInfo.authority, 0);
                             changed = true;
                         }
                     }
                 }
+                // Clear backoff for all sync services.
+                for (ComponentName service : mServices.keySet()) {
+                    SparseArray<AuthorityInfo> aInfos = mServices.get(service);
+                    for (int i = 0; i < aInfos.size(); i++) {
+                        AuthorityInfo authorityInfo = aInfos.valueAt(i);
+                        if (authorityInfo.backoffTime != NOT_IN_BACKOFF_MODE
+                                || authorityInfo.backoffDelay != NOT_IN_BACKOFF_MODE) {
+                            authorityInfo.backoffTime = NOT_IN_BACKOFF_MODE;
+                            authorityInfo.backoffDelay = NOT_IN_BACKOFF_MODE;
+                        }
+                    }
+                }
+                syncQueue.clearBackoffs();
             }
         }
 
@@ -742,28 +885,9 @@
         }
     }
 
-    public void setDelayUntilTime(Account account, int userId, String providerName,
-            long delayUntil) {
-        if (DEBUG) {
-            Log.v(TAG, "setDelayUntil: " + account + ", provider " + providerName
-                    + ", user " + userId + " -> delayUntil " + delayUntil);
-        }
+    public long getDelayUntilTime(EndPoint info) {
         synchronized (mAuthorities) {
-            AuthorityInfo authority = getOrCreateAuthorityLocked(
-                    account, userId, providerName, -1 /* ident */, true);
-            if (authority.delayUntil == delayUntil) {
-                return;
-            }
-            authority.delayUntil = delayUntil;
-        }
-
-        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
-    }
-
-    public long getDelayUntilTime(Account account, int userId, String providerName) {
-        synchronized (mAuthorities) {
-            AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
-                    "getDelayUntil");
+            AuthorityInfo authority = getAuthorityLocked(info, "getDelayUntil");
             if (authority == null) {
                 return 0;
             }
@@ -771,113 +895,148 @@
         }
     }
 
-    private void updateOrRemovePeriodicSync(PeriodicSync toUpdate, int userId, boolean add) {
-        if (DEBUG) {
-            Log.v(TAG, "addOrRemovePeriodicSync: " + toUpdate.account + ", user " + userId
-                    + ", provider " + toUpdate.authority
-                    + " -> period " + toUpdate.period + ", extras " + toUpdate.extras);
+    public void setDelayUntilTime(EndPoint info, long delayUntil) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "setDelayUntil: " + info
+                    + " -> delayUntil " + delayUntil);
         }
         synchronized (mAuthorities) {
-            if (toUpdate.period <= 0 && add) {
-                Log.e(TAG, "period < 0, should never happen in updateOrRemovePeriodicSync: add-"
-                        + add);
+            AuthorityInfo authority = getOrCreateAuthorityLocked(info, -1, true);
+            if (authority.delayUntil == delayUntil) {
+                return;
             }
-            if (toUpdate.extras == null) {
-                Log.e(TAG, "null extras, should never happen in updateOrRemovePeriodicSync: add-"
-                        + add);
+            authority.delayUntil = delayUntil;
+        }
+        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+    }
+
+    public void updateOrAddPeriodicSync(EndPoint info, long period, long flextime, Bundle extras) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "addPeriodicSync: " + info
+                    + " -> period " + period + ", flex " + flextime + ", extras "
+                    + extras.toString());
+        }
+        synchronized (mAuthorities) {
+            if (period <= 0) {
+                Log.e(TAG, "period < 0, should never happen in updateOrAddPeriodicSync");
+            }
+            if (extras == null) {
+                Log.e(TAG, "null extras, should never happen in updateOrAddPeriodicSync:");
             }
             try {
-                AuthorityInfo authority =
-                        getOrCreateAuthorityLocked(toUpdate.account, userId, toUpdate.authority,
-                                -1, false);
-                if (add) {
-                    // add this periodic sync if an equivalent periodic doesn't already exist.
-                    boolean alreadyPresent = false;
-                    for (int i = 0, N = authority.periodicSyncs.size(); i < N; i++) {
-                        PeriodicSync syncInfo = authority.periodicSyncs.get(i);
-                        if (PeriodicSync.syncExtrasEquals(
-                                toUpdate.extras,
-                                syncInfo.extras)) {
-                            if (toUpdate.period == syncInfo.period &&
-                                    toUpdate.flexTime == syncInfo.flexTime) {
-                                // Absolutely the same.
-                                return;
-                            }
-                            authority.periodicSyncs.set(i, new PeriodicSync(toUpdate));
-                            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(new PeriodicSync(toUpdate));
-                        SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
-                        status.setPeriodicSyncTime(authority.periodicSyncs.size() - 1, 0L);
-                    }
+                PeriodicSync toUpdate;
+                if (info.target_provider) {
+                    toUpdate = new PeriodicSync(info.account,
+                            info.provider,
+                            extras,
+                            period,
+                            flextime);
                 } else {
-                    // Remove any periodic syncs that match the authority and extras.
-                    SyncStatusInfo status = mSyncStatus.get(authority.ident);
-                    boolean changed = false;
-                    Iterator<PeriodicSync> iterator = authority.periodicSyncs.iterator();
-                    int i = 0;
-                    while (iterator.hasNext()) {
-                        PeriodicSync syncInfo = iterator.next();
-                        if (PeriodicSync.syncExtrasEquals(syncInfo.extras, toUpdate.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 {
-                                Log.e(TAG, "Tried removing sync status on remove periodic sync but"
-                                        + "did not find it.");
-                            }
-                        } else {
-                            i++;
+                    toUpdate = new PeriodicSync(info.service,
+                            extras,
+                            period,
+                            flextime);
+                }
+                AuthorityInfo authority =
+                        getOrCreateAuthorityLocked(info, -1, false);
+                // add this periodic sync if an equivalent periodic doesn't already exist.
+                boolean alreadyPresent = false;
+                for (int i = 0, N = authority.periodicSyncs.size(); i < N; i++) {
+                    PeriodicSync syncInfo = authority.periodicSyncs.get(i);
+                    if (SyncManager.syncExtrasEquals(syncInfo.extras,
+                            extras,
+                            true /* includeSyncSettings*/)) {
+                        if (period == syncInfo.period &&
+                                flextime == syncInfo.flexTime) {
+                            // Absolutely the same.
+                            Log.e(TAG, "update psync: exactly the same.");
+                            return;
                         }
+                        authority.periodicSyncs.set(i, toUpdate);
+                        alreadyPresent = true;
+                        break;
                     }
-                    if (!changed) {
-                        return;
-                    }
+                }
+                // 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(toUpdate);
+                    SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
+                    // A new periodic sync is initialised as already having been run.
+                    status.setPeriodicSyncTime(
+                            authority.periodicSyncs.size() - 1,
+                            System.currentTimeMillis());
                 }
             } finally {
                 writeAccountInfoLocked();
                 writeStatusLocked();
             }
         }
-
         reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
     }
 
-    public void addPeriodicSync(PeriodicSync toAdd, int userId) {
-        updateOrRemovePeriodicSync(toAdd, userId, true /* add */);
+    public void removePeriodicSync(EndPoint info, Bundle extras) {
+        synchronized(mAuthorities) {
+            try {
+                AuthorityInfo authority =
+                        getOrCreateAuthorityLocked(info, -1, false);
+                // Remove any periodic syncs that match the authority and extras.
+                SyncStatusInfo status = mSyncStatus.get(authority.ident);
+                boolean changed = false;
+                Iterator<PeriodicSync> iterator = authority.periodicSyncs.iterator();
+                int i = 0;
+                while (iterator.hasNext()) {
+                    PeriodicSync syncInfo = iterator.next();
+                    if (SyncManager.syncExtrasEquals(syncInfo.extras,
+                            extras,
+                            true /* includeSyncSettings */)) {
+                        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 {
+                            Log.e(TAG, "Tried removing sync status on remove periodic sync but"
+                                    + " did not find it.");
+                        }
+                    } else {
+                        i++;
+                    }
+                }
+                if (!changed) {
+                    return;
+                }
+            } finally {
+                writeAccountInfoLocked();
+                writeStatusLocked();
+            }
+        }
+        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
     }
 
-    public void removePeriodicSync(PeriodicSync toRemove, int userId) {
-        updateOrRemovePeriodicSync(toRemove, userId, false /* remove */);
-    }
-
-    public List<PeriodicSync> getPeriodicSyncs(Account account, int userId, String providerName) {
-        ArrayList<PeriodicSync> syncs = new ArrayList<PeriodicSync>();
+    /**
+     * @return list of periodic syncs for an authority. Never returns null - if no such syncs
+     * exist, returns an empty list.
+     */
+    public List<PeriodicSync> getPeriodicSyncs(EndPoint info) {
         synchronized (mAuthorities) {
-            AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
-                    "getPeriodicSyncs");
-            if (authority != null) {
-                for (PeriodicSync item : authority.periodicSyncs) {
+            AuthorityInfo authorityInfo = getAuthorityLocked(info, "getPeriodicSyncs");
+            ArrayList<PeriodicSync> syncs = new ArrayList<PeriodicSync>();
+            if (authorityInfo != null) {
+                for (PeriodicSync item : authorityInfo.periodicSyncs) {
                     // Copy and send out. Necessary for thread-safety although it's parceled.
                     syncs.add(new PeriodicSync(item));
                 }
             }
+            return syncs;
         }
-        return syncs;
     }
 
     public void setMasterSyncAutomatically(boolean flag, int userId) {
         synchronized (mAuthorities) {
             Boolean auto = mMasterSyncAutomatically.get(userId);
-            if (auto != null && (boolean) auto == flag) {
+            if (auto != null && auto == flag) {
                 return;
             }
             mMasterSyncAutomatically.put(userId, flag);
@@ -898,12 +1057,6 @@
         }
     }
 
-    public void removeAuthority(Account account, int userId, String authority) {
-        synchronized (mAuthorities) {
-            removeAuthorityLocked(account, userId, authority, true /* doWrite */);
-        }
-    }
-
     public AuthorityInfo getAuthority(int authorityId) {
         synchronized (mAuthorities) {
             return mAuthorities.get(authorityId);
@@ -911,53 +1064,47 @@
     }
 
     /**
-     * Returns true if there is currently a sync operation for the given
-     * account or authority actively being processed.
+     * Returns true if there is currently a sync operation being actively processed for the given
+     * authority.
      */
-    public boolean isSyncActive(Account account, int userId, String authority) {
+    public boolean isSyncActive(EndPoint info) {
         synchronized (mAuthorities) {
-            for (SyncInfo syncInfo : getCurrentSyncs(userId)) {
+            for (SyncInfo syncInfo : getCurrentSyncs(info.userId)) {
                 AuthorityInfo ainfo = getAuthority(syncInfo.authorityId);
-                if (ainfo != null && ainfo.account.equals(account)
-                        && ainfo.authority.equals(authority)
-                        && ainfo.userId == userId) {
+                if (ainfo != null && ainfo.base.matches(info)) {
                     return true;
                 }
             }
         }
-
         return false;
     }
 
-    public PendingOperation insertIntoPending(PendingOperation op) {
+    public PendingOperation insertIntoPending(SyncOperation op) {
+        PendingOperation pop;
         synchronized (mAuthorities) {
-            if (DEBUG) {
-                Log.v(TAG, "insertIntoPending: account=" + op.account
-                        + " user=" + op.userId
-                        + " auth=" + op.authority
-                        + " src=" + op.syncSource
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "insertIntoPending: authority=" + op.target
                         + " extras=" + op.extras);
             }
-
-            AuthorityInfo authority = getOrCreateAuthorityLocked(op.account, op.userId,
-                    op.authority,
-                    -1 /* desired identifier */,
-                    true /* write accounts to storage */);
+            final EndPoint info = op.target;
+            AuthorityInfo authority =
+                    getOrCreateAuthorityLocked(info,
+                            -1 /* desired identifier */,
+                            true /* write accounts to storage */);
             if (authority == null) {
                 return null;
             }
 
-            op = new PendingOperation(op);
-            op.authorityId = authority.ident;
-            mPendingOperations.add(op);
-            appendPendingOperationLocked(op);
+            pop = new PendingOperation(authority, op.reason, op.syncSource, op.extras,
+                    op.expedited);
+            mPendingOperations.add(pop);
+            writePendingOperationsLocked();
 
             SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
             status.pending = true;
         }
-
         reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
-        return op;
+        return pop;
     }
 
     /**
@@ -965,18 +1112,13 @@
      * authorities. If there are no more pending syncs for the same authority/account/userid,
      * update the SyncStatusInfo for that authority(authority here is the internal representation
      * of a 'sync operation'.
-     * @param op
-     * @return
+     * @param op Pending op to delete.
      */
     public boolean deleteFromPending(PendingOperation op) {
         boolean res = false;
         synchronized (mAuthorities) {
-            if (DEBUG) {
-                Log.v(TAG, "deleteFromPending: account=" + op.account
-                    + " user=" + op.userId
-                    + " auth=" + op.authority
-                    + " src=" + op.syncSource
-                    + " extras=" + op.extras);
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "deleteFromPending: account=" + op.toString());
             }
             if (mPendingOperations.remove(op)) {
                 if (mPendingOperations.size() == 0
@@ -986,30 +1128,27 @@
                 } else {
                     mNumPendingFinished++;
                 }
-
-                AuthorityInfo authority = getAuthorityLocked(op.account, op.userId, op.authority,
-                        "deleteFromPending");
+                AuthorityInfo authority = getAuthorityLocked(op.authority, "deleteFromPending");
                 if (authority != null) {
-                    if (DEBUG) Log.v(TAG, "removing - " + authority.toString());
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "removing - " + authority.toString());
+                    }
                     final int N = mPendingOperations.size();
                     boolean morePending = false;
-                    for (int i=0; i<N; i++) {
+                    for (int i = 0; i < N; i++) {
                         PendingOperation cur = mPendingOperations.get(i);
-                        if (cur.account.equals(op.account)
-                                && cur.authority.equals(op.authority)
-                                && cur.userId == op.userId) {
+                        if (cur.equals(op)) {
                             morePending = true;
                             break;
                         }
                     }
 
                     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;
                     }
                 }
-
                 res = true;
             }
         }
@@ -1044,7 +1183,7 @@
      */
     public void doDatabaseCleanup(Account[] accounts, int userId) {
         synchronized (mAuthorities) {
-            if (DEBUG) Log.v(TAG, "Updating for new accounts...");
+            if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "Updating for new accounts...");
             SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>();
             Iterator<AccountInfo> accIt = mAccounts.values().iterator();
             while (accIt.hasNext()) {
@@ -1052,7 +1191,7 @@
                 if (!ArrayUtils.contains(accounts, acc.accountAndUser.account)
                         && acc.accountAndUser.userId == userId) {
                     // This account no longer exists...
-                    if (DEBUG) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
                         Log.v(TAG, "Account removed: " + acc.accountAndUser);
                     }
                     for (AuthorityInfo auth : acc.authorities.values()) {
@@ -1099,25 +1238,25 @@
     public SyncInfo addActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
         final SyncInfo syncInfo;
         synchronized (mAuthorities) {
-            if (DEBUG) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "setActiveSync: account="
-                    + activeSyncContext.mSyncOperation.account
-                    + " auth=" + activeSyncContext.mSyncOperation.authority
+                    + " auth=" + activeSyncContext.mSyncOperation.target
                     + " src=" + activeSyncContext.mSyncOperation.syncSource
                     + " extras=" + activeSyncContext.mSyncOperation.extras);
             }
-            AuthorityInfo authority = getOrCreateAuthorityLocked(
-                    activeSyncContext.mSyncOperation.account,
-                    activeSyncContext.mSyncOperation.userId,
-                    activeSyncContext.mSyncOperation.authority,
+            final EndPoint info = activeSyncContext.mSyncOperation.target;
+            AuthorityInfo authorityInfo = getOrCreateAuthorityLocked(
+                    info,
                     -1 /* assign a new identifier if creating a new authority */,
                     true /* write to storage if this results in a change */);
-            syncInfo = new SyncInfo(authority.ident,
-                    authority.account, authority.authority,
+            syncInfo = new SyncInfo(
+                    authorityInfo.ident,
+                    authorityInfo.base.account,
+                    authorityInfo.base.provider,
+                    authorityInfo.base.service,
                     activeSyncContext.mStartTime);
-            getCurrentSyncs(authority.userId).add(syncInfo);
+            getCurrentSyncs(authorityInfo.base.userId).add(syncInfo);
         }
-
         reportActiveChange();
         return syncInfo;
     }
@@ -1127,10 +1266,11 @@
      */
     public void removeActiveSync(SyncInfo syncInfo, int userId) {
         synchronized (mAuthorities) {
-            if (DEBUG) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "removeActiveSync: account=" + syncInfo.account
                         + " user=" + userId
-                        + " auth=" + syncInfo.authority);
+                        + " auth=" + syncInfo.authority
+                        + " service=" + syncInfo.service);
             }
             getCurrentSyncs(userId).remove(syncInfo);
         }
@@ -1147,36 +1287,37 @@
 
     /**
      * Note that sync has started for the given account and authority.
+     *
+       syncOperation.account, syncOperation.userId, syncOperation.reason,
+                    syncOperation.authority,
+                    now, source, syncOperation.isInitialization(), syncOperation.extras
      */
-    public long insertStartSyncEvent(Account accountName, int userId, int reason,
-            String authorityName, long now, int source, boolean initialization, Bundle extras) {
+    public long insertStartSyncEvent(SyncOperation op, long now) {
         long id;
         synchronized (mAuthorities) {
-            if (DEBUG) {
-                Log.v(TAG, "insertStartSyncEvent: account=" + accountName + "user=" + userId
-                    + " auth=" + authorityName + " source=" + source);
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "insertStartSyncEvent: " + op);
             }
-            AuthorityInfo authority = getAuthorityLocked(accountName, userId, authorityName,
-                    "insertStartSyncEvent");
+            AuthorityInfo authority = getAuthorityLocked(op.target, "insertStartSyncEvent");
             if (authority == null) {
                 return -1;
             }
             SyncHistoryItem item = new SyncHistoryItem();
-            item.initialization = initialization;
+            item.initialization = op.isInitialization();
             item.authorityId = authority.ident;
             item.historyId = mNextHistoryId++;
             if (mNextHistoryId < 0) mNextHistoryId = 0;
             item.eventTime = now;
-            item.source = source;
-            item.reason = reason;
-            item.extras = extras;
+            item.source = op.syncSource;
+            item.reason = op.reason;
+            item.extras = op.extras;
             item.event = EVENT_START;
             mSyncHistory.add(0, item);
             while (mSyncHistory.size() > MAX_HISTORY) {
                 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);
@@ -1186,7 +1327,7 @@
     public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage,
             long downstreamActivity, long upstreamActivity) {
         synchronized (mAuthorities) {
-            if (DEBUG) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "stopSyncEvent: historyId=" + historyId);
             }
             SyncHistoryItem item = null;
@@ -1325,10 +1466,9 @@
     /**
      * Return a copy of the specified authority with the corresponding sync status
      */
-    public Pair<AuthorityInfo, SyncStatusInfo> getCopyOfAuthorityWithSyncStatus(
-            Account account, int userId, String authority) {
+    public Pair<AuthorityInfo, SyncStatusInfo> getCopyOfAuthorityWithSyncStatus(EndPoint info) {
         synchronized (mAuthorities) {
-            AuthorityInfo authorityInfo = getOrCreateAuthorityLocked(account, userId, authority,
+            AuthorityInfo authorityInfo = getOrCreateAuthorityLocked(info,
                     -1 /* assign a new identifier if creating a new authority */,
                     true /* write to storage if this results in a change */);
             return createCopyPairOfAuthorityWithSyncStatusLocked(authorityInfo);
@@ -1352,24 +1492,22 @@
     /**
      * Returns the status that matches the authority and account.
      *
-     * @param account the account we want to check
-     * @param authority the authority whose row should be selected
-     * @return the SyncStatusInfo for the authority
+     * @param info the target we want to check
+     * @return the SyncStatusInfo for the target
      */
-    public SyncStatusInfo getStatusByAccountAndAuthority(Account account, int userId,
-            String authority) {
-        if (account == null || authority == null) {
+    public SyncStatusInfo getStatusByAuthority(EndPoint info) {
+        if (info.target_provider && (info.account == null || info.provider == null)) {
           throw new IllegalArgumentException();
+        } else if (info.target_service && info.service == null) {
+            throw new IllegalArgumentException();
         }
         synchronized (mAuthorities) {
             final int N = mSyncStatus.size();
-            for (int i=0; i<N; i++) {
+            for (int i = 0; i < N; i++) {
                 SyncStatusInfo cur = mSyncStatus.valueAt(i);
                 AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
-
-                if (ainfo != null && ainfo.authority.equals(authority)
-                        && ainfo.userId == userId
-                        && account.equals(ainfo.account)) {
+                if (ainfo != null
+                        && ainfo.base.matches(info)) {
                   return cur;
                 }
             }
@@ -1377,25 +1515,20 @@
         }
     }
 
-    /**
-     * Return true if the pending status is true of any matching authorities.
-     */
-    public boolean isSyncPending(Account account, int userId, String authority) {
+    /** Return true if the pending status is true of any matching authorities. */
+    public boolean isSyncPending(EndPoint info) {
         synchronized (mAuthorities) {
             final int N = mSyncStatus.size();
-            for (int i=0; i<N; i++) {
+            for (int i = 0; i < N; i++) {
                 SyncStatusInfo cur = mSyncStatus.valueAt(i);
                 AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
                 if (ainfo == null) {
                     continue;
                 }
-                if (userId != ainfo.userId) {
+                if (!ainfo.base.matches(info)) {
                     continue;
                 }
-                if (account != null && !ainfo.account.equals(account)) {
-                    continue;
-                }
-                if (ainfo.authority.equals(authority) && cur.pending) {
+                if (cur.pending) {
                     return true;
                 }
             }
@@ -1453,126 +1586,131 @@
     /**
      * Retrieve an authority, returning null if one does not exist.
      *
-     * @param accountName The name of the account for the authority.
-     * @param authorityName The name of the authority itself.
+     * @param info container for the info of the authority to look up.
      * @param tag If non-null, this will be used in a log message if the
      * requested authority does not exist.
      */
-    private AuthorityInfo getAuthorityLocked(Account accountName, int userId, String authorityName,
-            String tag) {
-        AccountAndUser au = new AccountAndUser(accountName, userId);
-        AccountInfo accountInfo = mAccounts.get(au);
-        if (accountInfo == null) {
-            if (tag != null) {
-                if (DEBUG) {
-                    Log.v(TAG, tag + ": unknown account " + au);
-                }
+    private AuthorityInfo getAuthorityLocked(EndPoint info, String tag) {
+        if (info.target_service) {
+            SparseArray<AuthorityInfo> aInfo = mServices.get(info.service);
+            AuthorityInfo authority = null;
+            if (aInfo != null) {
+                authority = aInfo.get(info.userId);
             }
+            if (authority == null) {
+                if (tag != null) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, tag + " No authority info found for " + info.service + " for user "
+                                + info.userId);
+                    }
+                }
+                return null;
+            }
+            return authority;
+        } else if (info.target_provider){
+            AccountAndUser au = new AccountAndUser(info.account, info.userId);
+            AccountInfo accountInfo = mAccounts.get(au);
+            if (accountInfo == null) {
+                if (tag != null) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, tag + ": unknown account " + au);
+                    }
+                }
+                return null;
+            }
+            AuthorityInfo authority = accountInfo.authorities.get(info.provider);
+            if (authority == null) {
+                if (tag != null) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, tag + ": unknown provider " + info.provider);
+                    }
+                }
+                return null;
+            }
+            return authority;
+        } else {
+            Log.e(TAG, tag + " Authority : " + info + ", invalid target");
             return null;
         }
-        AuthorityInfo authority = accountInfo.authorities.get(authorityName);
-        if (authority == null) {
-            if (tag != null) {
-                if (DEBUG) {
-                    Log.v(TAG, tag + ": unknown authority " + authorityName);
-                }
-            }
-            return null;
-        }
-
-        return authority;
     }
 
     /**
-     * Retrieve an authority, returning null if one does not exist.
-     *
-     * @param service The service name used for this sync.
-     * @param userId The user for whom this sync is scheduled.
-     * @param tag If non-null, this will be used in a log message if the
-     * requested authority does not exist.
-     */
-    private AuthorityInfo getAuthorityLocked(ComponentName service, int userId, String tag) {
-        AuthorityInfo authority = mServices.get(service).get(userId);
-        if (authority == null) {
-            if (tag != null) {
-                if (DEBUG) {
-                    Log.v(TAG, tag + " No authority info found for " + service + " for user "
-                            + userId);
-                }
-            }
-            return null;
-        }
-        return authority;
-    }
-
-    /**
-     * @param cname identifier for the service.
-     * @param userId for the syncs corresponding to this authority.
+     * @param info info identifying authority.
      * @param ident unique identifier for authority. -1 for none.
      * @param doWrite if true, update the accounts.xml file on the disk.
-     * @return the authority that corresponds to the provided sync service, creating it if none
+     * @return the authority that corresponds to the provided sync authority, creating it if none
      * exists.
      */
-    private AuthorityInfo getOrCreateAuthorityLocked(ComponentName cname, int userId, int ident,
-            boolean doWrite) {
-        SparseArray<AuthorityInfo> aInfo = mServices.get(cname);
-        if (aInfo == null) {
-            aInfo = new SparseArray<AuthorityInfo>();
-            mServices.put(cname, aInfo);
-        }
-        AuthorityInfo authority = aInfo.get(userId);
-        if (authority == null) {
-            if (ident < 0) {
-                ident = mNextAuthorityId;
-                mNextAuthorityId++;
-                doWrite = true;
+    private AuthorityInfo getOrCreateAuthorityLocked(EndPoint info, int ident, boolean doWrite) {
+        AuthorityInfo authority = null;
+        if (info.target_service) {
+            SparseArray<AuthorityInfo> aInfo = mServices.get(info.service);
+            if (aInfo == null) {
+                aInfo = new SparseArray<AuthorityInfo>();
+                mServices.put(info.service, aInfo);
             }
-            if (DEBUG) {
-                Log.v(TAG, "created a new AuthorityInfo for " + cname.getPackageName()
-                        + ", " + cname.getClassName()
-                        + ", user: " + userId);
+            authority = aInfo.get(info.userId);
+            if (authority == null) {
+                authority = createAuthorityLocked(info, ident, doWrite);
+                aInfo.put(info.userId, authority);
             }
-            authority = new AuthorityInfo(cname, userId, ident);
-            aInfo.put(userId, authority);
-            mAuthorities.put(ident, authority);
-            if (doWrite) {
-                writeAccountInfoLocked();
+        } else if (info.target_provider) {
+            AccountAndUser au = new AccountAndUser(info.account, info.userId);
+            AccountInfo account = mAccounts.get(au);
+            if (account == null) {
+                account = new AccountInfo(au);
+                mAccounts.put(au, account);
+            }
+            authority = account.authorities.get(info.provider);
+            if (authority == null) {
+                authority = createAuthorityLocked(info, ident, doWrite);
+                account.authorities.put(info.provider, authority);
             }
         }
         return authority;
     }
 
-    private AuthorityInfo getOrCreateAuthorityLocked(Account accountName, int userId,
-            String authorityName, int ident, boolean doWrite) {
-        AccountAndUser au = new AccountAndUser(accountName, userId);
-        AccountInfo account = mAccounts.get(au);
-        if (account == null) {
-            account = new AccountInfo(au);
-            mAccounts.put(au, account);
+    private AuthorityInfo createAuthorityLocked(EndPoint info, int ident, boolean doWrite) {
+        AuthorityInfo authority;
+        if (ident < 0) {
+            ident = mNextAuthorityId;
+            mNextAuthorityId++;
+            doWrite = true;
         }
-        AuthorityInfo authority = account.authorities.get(authorityName);
-        if (authority == null) {
-            if (ident < 0) {
-                ident = mNextAuthorityId;
-                mNextAuthorityId++;
-                doWrite = true;
-            }
-            if (DEBUG) {
-                Log.v(TAG, "created a new AuthorityInfo for " + accountName
-                        + ", user " + userId
-                        + ", provider " + authorityName);
-            }
-            authority = new AuthorityInfo(accountName, userId, authorityName, ident);
-            account.authorities.put(authorityName, authority);
-            mAuthorities.put(ident, authority);
-            if (doWrite) {
-                writeAccountInfoLocked();
-            }
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "created a new AuthorityInfo for " + info);
         }
-
+        authority = new AuthorityInfo(info, ident);
+        mAuthorities.put(ident, authority);
+        if (doWrite) {
+            writeAccountInfoLocked();
+        }
         return authority;
     }
 
+    public void removeAuthority(EndPoint info) {
+        synchronized (mAuthorities) {
+            if (info.target_provider) {
+                removeAuthorityLocked(info.account, info.userId, info.provider, true /* doWrite */);
+            } else {
+                SparseArray<AuthorityInfo> aInfos = mServices.get(info.service);
+                if (aInfos != null) {
+                    AuthorityInfo authorityInfo = aInfos.get(info.userId);
+                    if (authorityInfo != null) {
+                        mAuthorities.remove(authorityInfo.ident);
+                        aInfos.delete(info.userId);
+                        writeAccountInfoLocked();
+                    }
+                }
+
+            }
+        }
+    }
+
+    /**
+     * Remove an authority associated with a provider. Needs to be a standalone function for
+     * backward compatibility.
+     */
     private void removeAuthorityLocked(Account account, int userId, String authorityName,
             boolean doWrite) {
         AccountInfo accountInfo = mAccounts.get(new AccountAndUser(account, userId));
@@ -1591,8 +1729,7 @@
      * Updates (in a synchronized way) the periodic sync time of the specified
      * authority id and target periodic sync
      */
-    public void setPeriodicSyncTime(
-            int authorityId, PeriodicSync targetPeriodicSync, long when) {
+    public void setPeriodicSyncTime(int authorityId, PeriodicSync targetPeriodicSync, long when) {
         boolean found = false;
         final AuthorityInfo authorityInfo;
         synchronized (mAuthorities) {
@@ -1608,7 +1745,7 @@
         }
         if (!found) {
             Log.w(TAG, "Ignoring setPeriodicSyncTime request for a sync that does not exist. " +
-                    "Authority: " + authorityInfo.authority);
+                    "Authority: " + authorityInfo.base);
         }
     }
 
@@ -1777,10 +1914,10 @@
 
         ArrayList<AuthorityInfo> authoritiesToRemove = new ArrayList<AuthorityInfo>();
         final int N = mAuthorities.size();
-        for (int i=0; i<N; i++) {
+        for (int i = 0; i < N; i++) {
             AuthorityInfo authority = mAuthorities.valueAt(i);
             // skip this authority if it isn't one of the renamed ones
-            final String newAuthorityName = sAuthorityRenames.get(authority.authority);
+            final String newAuthorityName = sAuthorityRenames.get(authority.base);
             if (newAuthorityName == null) {
                 continue;
             }
@@ -1796,20 +1933,26 @@
             }
 
             // if we already have a record of this new authority then don't copy over the settings
-            if (getAuthorityLocked(authority.account, authority.userId, newAuthorityName, "cleanup")
-                    != null) {
+            EndPoint newInfo =
+                    new EndPoint(authority.base.account,
+                            newAuthorityName,
+                            authority.base.userId);
+            if (getAuthorityLocked(newInfo, "cleanup") != null) {
                 continue;
             }
 
-            AuthorityInfo newAuthority = getOrCreateAuthorityLocked(authority.account,
-                    authority.userId, newAuthorityName, -1 /* ident */, false /* doWrite */);
+            AuthorityInfo newAuthority =
+                    getOrCreateAuthorityLocked(newInfo, -1 /* ident */, false /* doWrite */);
             newAuthority.enabled = true;
             writeNeeded = true;
         }
 
         for (AuthorityInfo authorityInfo : authoritiesToRemove) {
-            removeAuthorityLocked(authorityInfo.account, authorityInfo.userId,
-                    authorityInfo.authority, false /* doWrite */);
+            removeAuthorityLocked(
+                    authorityInfo.base.account,
+                    authorityInfo.base.userId,
+                    authorityInfo.base.provider,
+                    false /* doWrite */);
             writeNeeded = true;
         }
 
@@ -1865,15 +2008,18 @@
             }
             if (authority == null) {
                 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
-                    Log.v(TAG, "Creating entry");
+                    Log.v(TAG_FILE, "Creating authority entry");
                 }
                 if (accountName != null && accountType != null) {
-                    authority = getOrCreateAuthorityLocked(
-                            new Account(accountName, accountType), userId, authorityName, id,
-                                false);
+                    EndPoint info =
+                            new EndPoint(
+                                    new Account(accountName, accountType),
+                                    authorityName, userId);
+                    authority = getOrCreateAuthorityLocked(info, id, false);
                 } else {
-                    authority = getOrCreateAuthorityLocked(
-                            new ComponentName(packageName, className), userId, id, false);
+                    EndPoint info =
+                            new EndPoint(new ComponentName(packageName, className), userId);
+                    authority = getOrCreateAuthorityLocked(info, id, false);
                 }
                 // If the version is 0 then we are upgrading from a file format that did not
                 // know about periodic syncs. In that case don't clear the list since we
@@ -1905,7 +2051,7 @@
     /**
      * Parse a periodic sync from accounts.xml. Sets the bundle to be empty.
      */
-    private PeriodicSync parsePeriodicSync(XmlPullParser parser, AuthorityInfo authority) {
+    private PeriodicSync parsePeriodicSync(XmlPullParser parser, AuthorityInfo authorityInfo) {
         Bundle extras = new Bundle(); // Gets filled in later.
         String periodValue = parser.getAttributeValue(null, "period");
         String flexValue = parser.getAttributeValue(null, "flex");
@@ -1930,10 +2076,22 @@
             Log.d(TAG, "No flex time specified for this sync, using a default. period: "
             + period + " flex: " + flextime);
         }
-        final PeriodicSync periodicSync =
-                new PeriodicSync(authority.account, authority.authority, extras,
+        PeriodicSync periodicSync;
+        if (authorityInfo.base.target_provider) {
+            periodicSync =
+                new PeriodicSync(authorityInfo.base.account,
+                        authorityInfo.base.provider,
+                        extras,
                         period, flextime);
-        authority.periodicSyncs.add(periodicSync);
+        } else {
+            periodicSync =
+                    new PeriodicSync(
+                            authorityInfo.base.service,
+                            extras,
+                            period,
+                            flextime);
+        }
+        authorityInfo.periodicSyncs.add(periodicSync);
         return periodicSync;
     }
 
@@ -2001,17 +2159,18 @@
             final int N = mAuthorities.size();
             for (int i = 0; i < N; i++) {
                 AuthorityInfo authority = mAuthorities.valueAt(i);
+                EndPoint info = authority.base;
                 out.startTag(null, "authority");
                 out.attribute(null, "id", Integer.toString(authority.ident));
-                out.attribute(null, XML_ATTR_USER, Integer.toString(authority.userId));
+                out.attribute(null, XML_ATTR_USER, Integer.toString(info.userId));
                 out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(authority.enabled));
-                if (authority.service == null) {
-                    out.attribute(null, "account", authority.account.name);
-                    out.attribute(null, "type", authority.account.type);
-                    out.attribute(null, "authority", authority.authority);
+                if (info.service == null) {
+                    out.attribute(null, "account", info.account.name);
+                    out.attribute(null, "type", info.account.type);
+                    out.attribute(null, "authority", info.provider);
                 } else {
-                    out.attribute(null, "package", authority.service.getPackageName());
-                    out.attribute(null, "class", authority.service.getClassName());
+                    out.attribute(null, "package", info.service.getPackageName());
+                    out.attribute(null, "class", info.service.getClassName());
                 }
                 if (authority.syncable < 0) {
                     out.attribute(null, "syncable", "unknown");
@@ -2105,9 +2264,13 @@
                     accountType = "com.google";
                 }
                 String authorityName = c.getString(c.getColumnIndex("authority"));
-                AuthorityInfo authority = this.getOrCreateAuthorityLocked(
-                        new Account(accountName, accountType), 0 /* legacy is single-user */,
-                        authorityName, -1, false);
+                AuthorityInfo authority =
+                        this.getOrCreateAuthorityLocked(
+                                new EndPoint(new Account(accountName, accountType),
+                                        authorityName,
+                                        0 /* legacy is single-user */)
+                                , -1,
+                                false);
                 if (authority != null) {
                     int i = mSyncStatus.size();
                     boolean found = false;
@@ -2159,7 +2322,7 @@
                     while (i > 0) {
                         i--;
                         AuthorityInfo authority = mAuthorities.valueAt(i);
-                        if (authority.authority.equals(provider)) {
+                        if (authority.base.equals(provider)) {
                             authority.enabled = value == null || Boolean.parseBoolean(value);
                             authority.syncable = 1;
                         }
@@ -2275,62 +2438,52 @@
             String tagName = parser.getName();
             do {
                 PendingOperation pop = null;
-                if (eventType == XmlPullParser.START_TAG) {
-                    try {
-                        tagName = parser.getName();
-                        if (parser.getDepth() == 1 && "op".equals(tagName)) {
-                            // Verify version.
-                            String versionString =
-                                    parser.getAttributeValue(null, XML_ATTR_VERSION);
-                            if (versionString == null ||
-                                    Integer.parseInt(versionString) != PENDING_OPERATION_VERSION) {
-                                Log.w(TAG, "Unknown pending operation version " + versionString);
-                                throw new java.io.IOException("Unknown version.");
-                            }
-                            int authorityId = Integer.valueOf(parser.getAttributeValue(
-                                    null, XML_ATTR_AUTHORITYID));
-                            boolean expedited = Boolean.valueOf(parser.getAttributeValue(
-                                    null, XML_ATTR_EXPEDITED));
-                            int syncSource = Integer.valueOf(parser.getAttributeValue(
-                                    null, XML_ATTR_SOURCE));
-                            int reason = Integer.valueOf(parser.getAttributeValue(
-                                    null, XML_ATTR_REASON));
-                            AuthorityInfo authority = mAuthorities.get(authorityId);
-                            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
-                                Log.v(TAG_FILE, authorityId + " " + expedited + " " + syncSource + " "
-                                        + reason);
-                            }
-                            if (authority != null) {
-                                pop = new PendingOperation(
-                                        authority.account, authority.userId, reason,
-                                        syncSource, authority.authority, new Bundle(),
-                                        expedited);
-                                pop.flatExtras = null; // No longer used.
-                                mPendingOperations.add(pop);
+                    if (eventType == XmlPullParser.START_TAG) {
+                        try {
+                            tagName = parser.getName();
+                            if (parser.getDepth() == 2 && "op".equals(tagName)) {
+                                int authorityId = Integer.valueOf(parser.getAttributeValue(
+                                        null, XML_ATTR_AUTHORITYID));
+                                boolean expedited = Boolean.valueOf(parser.getAttributeValue(
+                                        null, XML_ATTR_EXPEDITED));
+                                int syncSource = Integer.valueOf(parser.getAttributeValue(
+                                        null, XML_ATTR_SOURCE));
+                                int reason = Integer.valueOf(parser.getAttributeValue(
+                                        null, XML_ATTR_REASON));
+                                AuthorityInfo authority = mAuthorities.get(authorityId);
                                 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
-                                    Log.v(TAG_FILE, "Adding pending op: "
+                                    Log.v(TAG_FILE, authorityId + " " + expedited + " " +
+                                            syncSource + " " + reason);
+                                }
+                                if (authority != null) {
+                                    pop = new PendingOperation(
+                                            authority, reason, syncSource, new Bundle(), expedited);
+                                    pop.flatExtras = null; // No longer used.
+                                    mPendingOperations.add(pop);
+                                    if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
+                                        Log.v(TAG_FILE, "Adding pending op: "
                                             + pop.authority
                                             + " src=" + pop.syncSource
                                             + " reason=" + pop.reason
                                             + " expedited=" + pop.expedited);
+                                    }
+                                } else {
+                                    // Skip non-existent authority.
+                                    pop = null;
+                                    if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
+                                        Log.v(TAG_FILE, "No authority found for " + authorityId
+                                                + ", skipping");
+                                    }
                                 }
-                            } else {
-                                // Skip non-existent authority.
-                                pop = null;
-                                if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
-                                    Log.v(TAG_FILE, "No authority found for " + authorityId
-                                            + ", skipping");
-                                }
+                            } else if (parser.getDepth() == 2 &&
+                                    pop != null &&
+                                    "extra".equals(tagName)) {
+                                parseExtra(parser, pop.extras);
                             }
-                        } else if (parser.getDepth() == 2 &&
-                                pop != null &&
-                                "extra".equals(tagName)) {
-                            parseExtra(parser, pop.extras);
+                        } catch (NumberFormatException e) {
+                            Log.d(TAG, "Invalid data in xml file.", e);
                         }
-                    } catch (NumberFormatException e) {
-                        Log.d(TAG, "Invalid data in xml file.", e);
                     }
-                }
                 eventType = parser.next();
             } while(eventType != XmlPullParser.END_DOCUMENT);
         } catch (java.io.IOException e) {
@@ -2348,98 +2501,6 @@
         }
     }
 
-    private static final String XML_ATTR_AUTHORITYID = "authority_id";
-    private static final String XML_ATTR_SOURCE = "source";
-    private static final String XML_ATTR_EXPEDITED = "expedited";
-    private static final String XML_ATTR_REASON = "reason";
-    private static final String XML_ATTR_VERSION = "version";
-
-    /**
-     * Write all currently pending ops to the pending ops file.
-     */
-    private void writePendingOperationsLocked() {
-        final int N = mPendingOperations.size();
-        FileOutputStream fos = null;
-        try {
-            if (N == 0) {
-                if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
-                    Log.v(TAG_FILE, "Truncating " + mPendingFile.getBaseFile());
-                }
-                mPendingFile.truncate();
-                return;
-            }
-            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
-                Log.v(TAG_FILE, "Writing new " + mPendingFile.getBaseFile());
-            }
-            fos = mPendingFile.startWrite();
-            XmlSerializer out = new FastXmlSerializer();
-            out.setOutput(fos, "utf-8");
-
-            for (int i = 0; i < N; i++) {
-                PendingOperation pop = mPendingOperations.get(i);
-                writePendingOperationLocked(pop, out);
-            }
-            out.endDocument();
-            mPendingFile.finishWrite(fos);
-        } catch (java.io.IOException e1) {
-            Log.w(TAG, "Error writing pending operations", e1);
-            if (fos != null) {
-                mPendingFile.failWrite(fos);
-            }
-        }
-    }
-
-    /** Write all currently pending ops to the pending ops file. */
-     private void writePendingOperationLocked(PendingOperation pop, XmlSerializer out)
-             throws IOException {
-         // Pending operation.
-         out.startTag(null, "op");
-
-         out.attribute(null, XML_ATTR_VERSION, Integer.toString(PENDING_OPERATION_VERSION));
-         out.attribute(null, XML_ATTR_AUTHORITYID, Integer.toString(pop.authorityId));
-         out.attribute(null, XML_ATTR_SOURCE, Integer.toString(pop.syncSource));
-         out.attribute(null, XML_ATTR_EXPEDITED, Boolean.toString(pop.expedited));
-         out.attribute(null, XML_ATTR_REASON, Integer.toString(pop.reason));
-         extrasToXml(out, pop.extras);
-
-         out.endTag(null, "op");
-     }
-
-    /**
-     * Append the given operation to the pending ops file; if unable to,
-     * write all pending ops.
-     */
-    private void appendPendingOperationLocked(PendingOperation op) {
-        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
-            Log.v(TAG, "Appending to " + mPendingFile.getBaseFile());
-        }
-        FileOutputStream fos = null;
-        try {
-            fos = mPendingFile.openAppend();
-        } catch (java.io.IOException e) {
-            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
-                Log.v(TAG, "Failed append; writing full file");
-            }
-            writePendingOperationsLocked();
-            return;
-        }
-
-        try {
-            XmlSerializer out = new FastXmlSerializer();
-            out.setOutput(fos, "utf-8");
-            writePendingOperationLocked(op, out);
-            out.endDocument();
-            mPendingFile.finishWrite(fos);
-        } catch (java.io.IOException e1) {
-            Log.w(TAG, "Error writing appending operation", e1);
-            mPendingFile.failWrite(fos);
-        } finally {
-            try {
-                fos.close();
-            } catch (IOException e) {}
-        }
-    }
-
     static private byte[] flattenBundle(Bundle bundle) {
         byte[] flatData = null;
         Parcel parcel = Parcel.obtain();
@@ -2469,6 +2530,60 @@
         return bundle;
     }
 
+    private static final String XML_ATTR_AUTHORITYID = "authority_id";
+    private static final String XML_ATTR_SOURCE = "source";
+    private static final String XML_ATTR_EXPEDITED = "expedited";
+    private static final String XML_ATTR_REASON = "reason";
+
+    /**
+     * Write all currently pending ops to the pending ops file.
+     * TODO: Change this from xml so that we can append to this file as before.
+     */
+    private void writePendingOperationsLocked() {
+        final int N = mPendingOperations.size();
+        FileOutputStream fos = null;
+        try {
+            if (N == 0) {
+                if (Log.isLoggable(TAG_FILE, Log.VERBOSE)){
+                    Log.v(TAG, "Truncating " + mPendingFile.getBaseFile());
+                }
+                mPendingFile.truncate();
+                return;
+            }
+            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
+                Log.v(TAG, "Writing new " + mPendingFile.getBaseFile());
+            }
+            fos = mPendingFile.startWrite();
+            XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(fos, "utf-8");
+            out.startDocument(null, true);
+            out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+            out.startTag(null, "pending");
+            out.attribute(null, "version", Integer.toString(PENDING_OPERATION_VERSION));
+
+            for (int i = 0; i < N; i++) {
+                PendingOperation pop = mPendingOperations.get(i);
+                out.startTag(null, "op");
+                out.attribute(null, XML_ATTR_AUTHORITYID, Integer.toString(pop.authorityId));
+                out.attribute(null, XML_ATTR_SOURCE, Integer.toString(pop.syncSource));
+                out.attribute(null, XML_ATTR_EXPEDITED, Boolean.toString(pop.expedited));
+                out.attribute(null, XML_ATTR_REASON, Integer.toString(pop.reason));
+                extrasToXml(out, pop.extras);
+                out.endTag(null, "op");
+             }
+             out.endTag(null, "pending");
+             out.endDocument();
+             mPendingFile.finishWrite(fos);
+        } catch (java.io.IOException e1) {
+            Log.w(TAG, "Error writing pending operations", e1);
+            if (fos != null) {
+                mPendingFile.failWrite(fos);
+            }
+        }
+    }
+
+
     private void extrasToXml(XmlSerializer out, Bundle extras) throws java.io.IOException {
         for (String key : extras.keySet()) {
             out.startTag(null, "extra");
@@ -2501,6 +2616,26 @@
         }
     }
 
+    private void requestSync(AuthorityInfo authorityInfo, int reason, Bundle extras) {
+        if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID
+                && mSyncRequestListener != null) {
+            mSyncRequestListener.onSyncRequest(authorityInfo.base, reason, extras);
+        } else {
+            SyncRequest.Builder req =
+                    new SyncRequest.Builder()
+                        .syncOnce(0, 0)
+                        .setExtras(extras);
+            if (authorityInfo.base.target_provider) {
+                req.setSyncAdapter(
+                        authorityInfo.base.account,
+                        authorityInfo.base.provider);
+            } else {
+                req.setSyncAdapter(authorityInfo.base.service);
+            }
+            ContentResolver.requestSync(req.build());
+        }
+    }
+
     private void requestSync(Account account, int userId, int reason, String authority,
             Bundle extras) {
         // If this is happening in the system process, then call the syncrequest listener
@@ -2509,7 +2644,10 @@
         // which will know which userId to apply based on the Binder id.
         if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID
                 && mSyncRequestListener != null) {
-            mSyncRequestListener.onSyncRequest(account, userId, reason, authority, extras);
+            mSyncRequestListener.onSyncRequest(
+                    new EndPoint(account, authority, userId),
+                    reason,
+                    extras);
         } else {
             ContentResolver.requestSync(account, authority, extras);
         }
@@ -2605,10 +2743,8 @@
     public void dumpPendingOperations(StringBuilder sb) {
         sb.append("Pending Ops: ").append(mPendingOperations.size()).append(" operation(s)\n");
         for (PendingOperation pop : mPendingOperations) {
-            sb.append("(" + pop.account)
-                .append(", u" + pop.userId)
-                .append(", " + pop.authority)
-                .append(", " + pop.extras)
+            sb.append("(info: " + pop.authority.toString())
+                .append(", extras: " + pop.extras)
                 .append(")\n");
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
index e44652f..853d2a2 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
@@ -17,6 +17,7 @@
 package com.android.server.content;
 
 import android.accounts.Account;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -32,22 +33,33 @@
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.server.content.SyncStorageEngine.EndPoint;
+
 import com.android.internal.os.AtomicFile;
 
 import java.io.File;
 import java.io.FileOutputStream;
 import java.util.List;
 
+import com.android.server.content.SyncStorageEngine.EndPoint;
+
 public class SyncStorageEngineTest extends AndroidTestCase {
 
     protected Account account1;
+    protected ComponentName syncService1;
     protected String authority1 = "testprovider";
     protected Bundle defaultBundle;
     protected final int DEFAULT_USER = 0;
-    
+
+    /* Some default poll frequencies. */
+    final long dayPoll = (60 * 60 * 24);
+    final long dayFuzz = 60;
+    final long thousandSecs = 1000;
+    final long thousandSecsFuzz = 100;
+
     MockContentResolver mockResolver;
     SyncStorageEngine engine;
-    
+
     private File getSyncDir() {
         return new File(new File(getContext().getFilesDir(), "system"), "sync");
     }
@@ -55,6 +67,7 @@
     @Override
     public void setUp() {
         account1 = new Account("a@example.com", "example.type");
+        syncService1 = new ComponentName("com.example", "SyncService");
         // Default bundle.
         defaultBundle = new Bundle();
         defaultBundle.putInt("int_key", 0);
@@ -80,11 +93,13 @@
 
         SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
                 new TestContext(mockResolver, getContext()));
-
         long time0 = 1000;
-        long historyId = engine.insertStartSyncEvent(
-                account, 0, SyncOperation.REASON_PERIODIC, authority, time0,
-                SyncStorageEngine.SOURCE_LOCAL, false /* initialization */, null /* extras */);
+        SyncOperation op = new SyncOperation(account, 0,
+                SyncOperation.REASON_PERIODIC,
+                SyncStorageEngine.SOURCE_LOCAL,
+                authority,
+                null, time0, 0 /* flex*/, 0, 0, true);
+        long historyId = engine.insertStartSyncEvent(op, time0);
         long time1 = time0 + SyncStorageEngine.MILLIS_IN_4WEEKS * 2;
         engine.stopSyncEvent(historyId, time1 - time0, "yay", 0, 0);
     }
@@ -94,27 +109,28 @@
      */
     @MediumTest
     public void testPending() throws Exception {
-        SyncStorageEngine.PendingOperation pop =
-                new SyncStorageEngine.PendingOperation(account1, DEFAULT_USER,
-                        SyncOperation.REASON_PERIODIC, SyncStorageEngine.SOURCE_LOCAL,
-                        authority1, defaultBundle, false);
-        
-        engine.insertIntoPending(pop);
+        SyncOperation sop = new SyncOperation(account1,
+                DEFAULT_USER,
+                SyncOperation.REASON_PERIODIC,
+                SyncStorageEngine.SOURCE_LOCAL, authority1, null,
+                0 /* runtime */, 0 /* flex */, 0 /* backoff */, 0 /* delayuntil */,
+                true /* expedited */);
+        engine.insertIntoPending(sop);
+
         // Force engine to read from disk.
         engine.clearAndReadState();
 
         assert(engine.getPendingOperationCount() == 1);
         List<SyncStorageEngine.PendingOperation> pops = engine.getPendingOperations();
         SyncStorageEngine.PendingOperation popRetrieved = pops.get(0);
-        assertEquals(pop.account, popRetrieved.account);
-        assertEquals(pop.reason, popRetrieved.reason);
-        assertEquals(pop.userId, popRetrieved.userId);
-        assertEquals(pop.syncSource, popRetrieved.syncSource);
-        assertEquals(pop.authority, popRetrieved.authority);
-        assertEquals(pop.expedited, popRetrieved.expedited);
-        assertEquals(pop.serviceName, popRetrieved.serviceName);
-        assert(android.content.PeriodicSync.syncExtrasEquals(pop.extras, popRetrieved.extras));
-
+        assertEquals(sop.target.account, popRetrieved.authority.account);
+        assertEquals(sop.target.provider, popRetrieved.authority.provider);
+        assertEquals(sop.target.service, popRetrieved.authority.service);
+        assertEquals(sop.target.userId, popRetrieved.authority.userId);
+        assertEquals(sop.reason, popRetrieved.reason);
+        assertEquals(sop.syncSource, popRetrieved.syncSource);
+        assertEquals(sop.expedited, popRetrieved.expedited);
+        assert(android.content.PeriodicSync.syncExtrasEquals(sop.extras, popRetrieved.extras));
     }
 
     /**
@@ -134,42 +150,44 @@
         final int period2 = 1000;
 
         PeriodicSync sync1 = new PeriodicSync(account1, authority, extras1, period1);
+        EndPoint end1 = new EndPoint(account1, authority, 0);
+
         PeriodicSync sync2 = new PeriodicSync(account1, authority, extras2, period1);
         PeriodicSync sync3 = new PeriodicSync(account1, authority, extras2, period2);
         PeriodicSync sync4 = new PeriodicSync(account2, authority, extras2, period2);
 
-        
+
 
         removePeriodicSyncs(engine, account1, 0, authority);
         removePeriodicSyncs(engine, account2, 0, authority);
         removePeriodicSyncs(engine, account1, 1, authority);
 
         // this should add two distinct periodic syncs for account1 and one for account2
-        engine.addPeriodicSync(sync1, 0);
-        engine.addPeriodicSync(sync2, 0);
-        engine.addPeriodicSync(sync3, 0);
-        engine.addPeriodicSync(sync4, 0);
+        engine.updateOrAddPeriodicSync(new EndPoint(account1, authority, 0), period1, 0, extras1);
+        engine.updateOrAddPeriodicSync(new EndPoint(account1, authority, 0), period1, 0, extras2);
+        engine.updateOrAddPeriodicSync(new EndPoint(account1, authority, 0), period2, 0, extras2);
+        engine.updateOrAddPeriodicSync(new EndPoint(account2, authority, 0), period2, 0, extras2);
         // add a second user
-        engine.addPeriodicSync(sync2, 1);
+        engine.updateOrAddPeriodicSync(new EndPoint(account1, authority, 1), period1, 0, extras2);
 
-        List<PeriodicSync> syncs = engine.getPeriodicSyncs(account1, 0, authority);
+        List<PeriodicSync> syncs = engine.getPeriodicSyncs(new EndPoint(account1, authority, 0));
 
         assertEquals(2, syncs.size());
 
         assertEquals(sync1, syncs.get(0));
         assertEquals(sync3, syncs.get(1));
 
-        engine.removePeriodicSync(sync1, 0);
+        engine.removePeriodicSync(new EndPoint(account1, authority, 0), extras1);
 
-        syncs = engine.getPeriodicSyncs(account1, 0, authority);
+        syncs = engine.getPeriodicSyncs(new EndPoint(account1, authority, 0));
         assertEquals(1, syncs.size());
         assertEquals(sync3, syncs.get(0));
 
-        syncs = engine.getPeriodicSyncs(account2, 0, authority);
+        syncs = engine.getPeriodicSyncs(new EndPoint(account2, authority, 0));
         assertEquals(1, syncs.size());
         assertEquals(sync4, syncs.get(0));
 
-        syncs = engine.getPeriodicSyncs(sync2.account, 1, sync2.authority);
+        syncs = engine.getPeriodicSyncs(new EndPoint(sync2.account, sync2.authority, 1));
         assertEquals(1, syncs.size());
         assertEquals(sync2, syncs.get(0));
     }
@@ -190,12 +208,19 @@
         final int period2 = 1000;
         final int flex1 = 10;
         final int flex2 = 100;
+        EndPoint point1 = new EndPoint(account1, authority, 0);
+        EndPoint point2 = new EndPoint(account2, authority, 0);
+        EndPoint point1User2 = new EndPoint(account1, authority, 1);
 
         PeriodicSync sync1 = new PeriodicSync(account1, authority, extras1, period1, flex1);
         PeriodicSync sync2 = new PeriodicSync(account1, authority, extras2, period1, flex1);
         PeriodicSync sync3 = new PeriodicSync(account1, authority, extras2, period2, flex2);
         PeriodicSync sync4 = new PeriodicSync(account2, authority, extras2, period2, flex2);
 
+        EndPoint target1 = new EndPoint(account1, authority, 0);
+        EndPoint target2 = new EndPoint(account2, authority, 0);
+        EndPoint target1UserB = new EndPoint(account1, authority, 1);
+
         MockContentResolver mockResolver = new MockContentResolver();
 
         SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
@@ -206,40 +231,42 @@
         removePeriodicSyncs(engine, account1, 1, authority);
 
         // This should add two distinct periodic syncs for account1 and one for account2
-        engine.addPeriodicSync(sync1, 0);
-        engine.addPeriodicSync(sync2, 0);
-        engine.addPeriodicSync(sync3, 0); // Should edit sync2 and update the period.
-        engine.addPeriodicSync(sync4, 0);
-        // add a second user
-        engine.addPeriodicSync(sync2, 1);
+        engine.updateOrAddPeriodicSync(target1, period1, flex1, extras1);
+        engine.updateOrAddPeriodicSync(target1, period1, flex1, extras2);
+        // Edit existing sync and update the period and flex.
+        engine.updateOrAddPeriodicSync(target1, period2, flex2, extras2);
+        engine.updateOrAddPeriodicSync(target2, period2, flex2, extras2);
+        // add a target for a second user.
+        engine.updateOrAddPeriodicSync(target1UserB, period1, flex1, extras2);
 
-        List<PeriodicSync> syncs = engine.getPeriodicSyncs(account1, 0, authority);
+        List<PeriodicSync> syncs = engine.getPeriodicSyncs(target1);
 
         assertEquals(2, syncs.size());
 
         assertEquals(sync1, syncs.get(0));
         assertEquals(sync3, syncs.get(1));
 
-        engine.removePeriodicSync(sync1, 0);
+        engine.removePeriodicSync(target1, extras1);
 
-        syncs = engine.getPeriodicSyncs(account1, 0, authority);
+        syncs = engine.getPeriodicSyncs(target1);
         assertEquals(1, syncs.size());
         assertEquals(sync3, syncs.get(0));
 
-        syncs = engine.getPeriodicSyncs(account2, 0, authority);
+        syncs = engine.getPeriodicSyncs(target2);
         assertEquals(1, syncs.size());
         assertEquals(sync4, syncs.get(0));
 
-        syncs = engine.getPeriodicSyncs(sync2.account, 1, sync2.authority);
+        syncs = engine.getPeriodicSyncs(target1UserB);
         assertEquals(1, syncs.size());
         assertEquals(sync2, syncs.get(0));
     }
 
     private void removePeriodicSyncs(SyncStorageEngine engine, Account account, int userId, String authority) {
-        engine.setIsSyncable(account, userId, authority, engine.getIsSyncable(account, 0, authority));
-        List<PeriodicSync> syncs = engine.getPeriodicSyncs(account, userId, authority);
+        EndPoint target = new EndPoint(account, authority, userId);
+        engine.setIsSyncable(account, userId, authority, engine.getIsSyncable(account, userId, authority));
+        List<PeriodicSync> syncs = engine.getPeriodicSyncs(target);
         for (PeriodicSync sync : syncs) {
-            engine.removePeriodicSync(sync, userId);
+            engine.removePeriodicSync(target, sync.extras);
         }
     }
 
@@ -264,16 +291,19 @@
         final int flex1 = 10;
         final int flex2 = 100;
 
+        EndPoint point1 = new EndPoint(account1, authority1, 0);
+        EndPoint point2 = new EndPoint(account1, authority2, 0);
+        EndPoint point3 = new EndPoint(account2, authority1, 0);
+
         PeriodicSync sync1 = new PeriodicSync(account1, authority1, extras1, period1, flex1);
         PeriodicSync sync2 = new PeriodicSync(account1, authority1, extras2, period1, flex1);
         PeriodicSync sync3 = new PeriodicSync(account1, authority2, extras1, period1, flex1);
         PeriodicSync sync4 = new PeriodicSync(account1, authority2, extras2, period2, flex2);
         PeriodicSync sync5 = new PeriodicSync(account2, authority1, extras1, period1, flex1);
 
-        MockContentResolver mockResolver = new MockContentResolver();
-
-        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
-                new TestContext(mockResolver, getContext()));
+        EndPoint target1 = new EndPoint(account1, authority1, 0);
+        EndPoint target2 = new EndPoint(account1, authority2, 0);
+        EndPoint target3 = new EndPoint(account2, authority1, 0);
 
         removePeriodicSyncs(engine, account1, 0, authority1);
         removePeriodicSyncs(engine, account2, 0, authority1);
@@ -294,26 +324,26 @@
         engine.setIsSyncable(account2, 0, authority2, 0);
         engine.setSyncAutomatically(account2, 0, authority2, true);
 
-        engine.addPeriodicSync(sync1, 0);
-        engine.addPeriodicSync(sync2, 0);
-        engine.addPeriodicSync(sync3, 0);
-        engine.addPeriodicSync(sync4, 0);
-        engine.addPeriodicSync(sync5, 0);
+        engine.updateOrAddPeriodicSync(target1, period1, flex1, extras1);
+        engine.updateOrAddPeriodicSync(target1, period1, flex1, extras2);
+        engine.updateOrAddPeriodicSync(target2, period1, flex1, extras1);
+        engine.updateOrAddPeriodicSync(target2, period2, flex2, extras2);
+        engine.updateOrAddPeriodicSync(target3, period1, flex1, extras1);
 
         engine.writeAllState();
         engine.clearAndReadState();
 
-        List<PeriodicSync> syncs = engine.getPeriodicSyncs(account1, 0, authority1);
+        List<PeriodicSync> syncs = engine.getPeriodicSyncs(target1);
         assertEquals(2, syncs.size());
         assertEquals(sync1, syncs.get(0));
         assertEquals(sync2, syncs.get(1));
 
-        syncs = engine.getPeriodicSyncs(account1, 0, authority2);
+        syncs = engine.getPeriodicSyncs(target2);
         assertEquals(2, syncs.size());
         assertEquals(sync3, syncs.get(0));
         assertEquals(sync4, syncs.get(1));
 
-        syncs = engine.getPeriodicSyncs(account2, 0, authority1);
+        syncs = engine.getPeriodicSyncs(target3);
         assertEquals(1, syncs.size());
         assertEquals(sync5, syncs.get(0));
 
@@ -328,6 +358,35 @@
         assertEquals(0, engine.getIsSyncable(account2, 0, authority2));
     }
 
+    @SmallTest
+    public void testComponentParsing() throws Exception {
+        // Sync Service component.
+        PeriodicSync sync1 = new PeriodicSync(syncService1, Bundle.EMPTY, dayPoll, dayFuzz);
+
+        byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<accounts version=\"2\" >\n"
+                + "<authority id=\"0\" user=\"0\" package=\"" + syncService1.getPackageName() + "\""
+                + " class=\"" + syncService1.getClassName() + "\" syncable=\"true\">"
+                + "\n<periodicSync period=\"" + dayPoll + "\" flex=\"" + dayFuzz + "\"/>"
+                + "\n</authority>"
+                + "</accounts>").getBytes();
+
+        File syncDir = getSyncDir();
+        syncDir.mkdirs();
+        AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
+        FileOutputStream fos = accountInfoFile.startWrite();
+        fos.write(accountsFileData);
+        accountInfoFile.finishWrite(fos);
+
+        engine.clearAndReadState();
+
+        // Test service component read
+        List<PeriodicSync> syncs = engine.getPeriodicSyncs(
+                new SyncStorageEngine.EndPoint(syncService1, 0));
+        assertEquals(1, syncs.size());
+        assertEquals(1, engine.getIsTargetServiceActive(syncService1, 0));
+    }
+
     @MediumTest
     /**
      * V2 introduces flex time as well as service components.
@@ -339,20 +398,20 @@
         final String authority2 = "auth2";
         final String authority3 = "auth3";
 
-        final long dayPoll = (60 * 60 * 24);
-        final long dayFuzz = 60;
-        final long thousandSecs = 1000;
-        final long thousandSecsFuzz = 100;
-        final Bundle extras = new Bundle();
-        PeriodicSync sync1 = new PeriodicSync(account, authority1, extras, dayPoll, dayFuzz);
-        PeriodicSync sync2 = new PeriodicSync(account, authority2, extras, dayPoll, dayFuzz);
-        PeriodicSync sync3 = new PeriodicSync(account, authority3, extras, dayPoll, dayFuzz);
-        PeriodicSync sync1s = new PeriodicSync(account, authority1, extras, thousandSecs, thousandSecsFuzz);
-        PeriodicSync sync2s = new PeriodicSync(account, authority2, extras, thousandSecs, thousandSecsFuzz);
-        PeriodicSync sync3s = new PeriodicSync(account, authority3, extras, thousandSecs, thousandSecsFuzz);
-        MockContentResolver mockResolver = new MockContentResolver();
+        EndPoint target1 = new EndPoint(account, authority1, 0);
+        EndPoint target2 = new EndPoint(account, authority2, 0);
+        EndPoint target3 = new EndPoint(account, authority3, 0);
+        EndPoint target4 = new EndPoint(account, authority3, 1);
 
-        final TestContext testContext = new TestContext(mockResolver, getContext());
+        PeriodicSync sync1 = new PeriodicSync(account, authority1, Bundle.EMPTY, dayPoll, dayFuzz);
+        PeriodicSync sync2 = new PeriodicSync(account, authority2, Bundle.EMPTY, dayPoll, dayFuzz);
+        PeriodicSync sync3 = new PeriodicSync(account, authority3, Bundle.EMPTY, dayPoll, dayFuzz);
+        PeriodicSync sync1s = new PeriodicSync(account, authority1, Bundle.EMPTY, thousandSecs,
+                thousandSecsFuzz);
+        PeriodicSync sync2s = new PeriodicSync(account, authority2, Bundle.EMPTY, thousandSecs,
+                thousandSecsFuzz);
+        PeriodicSync sync3s = new PeriodicSync(account, authority3, Bundle.EMPTY, thousandSecs,
+                thousandSecsFuzz);
 
         byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
                 + "<accounts version=\"2\" >\n"
@@ -378,21 +437,22 @@
         fos.write(accountsFileData);
         accountInfoFile.finishWrite(fos);
 
-        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
+        engine.clearAndReadState();
 
-        List<PeriodicSync> syncs = engine.getPeriodicSyncs(account, 0, authority1);
+        List<PeriodicSync> syncs = engine.getPeriodicSyncs(target1);
         assertEquals("Got incorrect # of syncs", 1, syncs.size());
         assertEquals(sync1, syncs.get(0));
 
-        syncs = engine.getPeriodicSyncs(account, 0, authority2);
+        syncs = engine.getPeriodicSyncs(target2);
         assertEquals(1, syncs.size());
         assertEquals(sync2, syncs.get(0));
 
-        syncs = engine.getPeriodicSyncs(account, 0, authority3);
+        syncs = engine.getPeriodicSyncs(target3);
         assertEquals(1, syncs.size());
         assertEquals(sync3, syncs.get(0));
 
-        syncs = engine.getPeriodicSyncs(account, 1, authority3);
+        syncs = engine.getPeriodicSyncs(target4);
+
         assertEquals(1, syncs.size());
         assertEquals(sync3, syncs.get(0));
 
@@ -411,13 +471,13 @@
 
         engine.clearAndReadState();
 
-        syncs = engine.getPeriodicSyncs(account, 0, authority1);
+        syncs = engine.getPeriodicSyncs(target1);
         assertEquals(0, syncs.size());
 
-        syncs = engine.getPeriodicSyncs(account, 0, authority2);
+        syncs = engine.getPeriodicSyncs(target2);
         assertEquals(0, syncs.size());
 
-        syncs = engine.getPeriodicSyncs(account, 0, authority3);
+        syncs = engine.getPeriodicSyncs(target3);
         assertEquals(0, syncs.size());
 
         accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
@@ -440,15 +500,15 @@
 
         engine.clearAndReadState();
 
-        syncs = engine.getPeriodicSyncs(account, 0, authority1);
+        syncs = engine.getPeriodicSyncs(target1);
         assertEquals(1, syncs.size());
         assertEquals(sync1s, syncs.get(0));
 
-        syncs = engine.getPeriodicSyncs(account, 0, authority2);
+        syncs = engine.getPeriodicSyncs(target2);
         assertEquals(1, syncs.size());
         assertEquals(sync2s, syncs.get(0));
 
-        syncs = engine.getPeriodicSyncs(account, 0, authority3);
+        syncs = engine.getPeriodicSyncs(target3);
         assertEquals(1, syncs.size());
         assertEquals(sync3s, syncs.get(0));
     }
@@ -460,6 +520,12 @@
         final String authority2 = "auth2";
         final String authority3 = "auth3";
         final Bundle extras = new Bundle();
+
+        EndPoint target1 = new EndPoint(account, authority1, 0);
+        EndPoint target2 = new EndPoint(account, authority2, 0);
+        EndPoint target3 = new EndPoint(account, authority3, 0);
+        EndPoint target4 = new EndPoint(account, authority3, 1);
+
         PeriodicSync sync1 = new PeriodicSync(account, authority1, extras, (long) (60 * 60 * 24));
         PeriodicSync sync2 = new PeriodicSync(account, authority2, extras, (long) (60 * 60 * 24));
         PeriodicSync sync3 = new PeriodicSync(account, authority3, extras, (long) (60 * 60 * 24));
@@ -488,19 +554,20 @@
 
         SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
 
-        List<PeriodicSync> syncs = engine.getPeriodicSyncs(account, 0, authority1);
+        List<PeriodicSync> syncs = engine.getPeriodicSyncs(target1);
         assertEquals(1, syncs.size());
         assertEquals("expected sync1: " + sync1.toString() + " == sync 2" + syncs.get(0).toString(), sync1, syncs.get(0));
 
-        syncs = engine.getPeriodicSyncs(account, 0, authority2);
+        syncs = engine.getPeriodicSyncs(target2);
         assertEquals(1, syncs.size());
         assertEquals(sync2, syncs.get(0));
 
-        syncs = engine.getPeriodicSyncs(account, 0, authority3);
+        syncs = engine.getPeriodicSyncs(target3);
         assertEquals(1, syncs.size());
         assertEquals(sync3, syncs.get(0));
+        syncs = engine.getPeriodicSyncs(target4);
 
-        syncs = engine.getPeriodicSyncs(account, 1, authority3);
+
         assertEquals(1, syncs.size());
         assertEquals(sync3, syncs.get(0));
 
@@ -518,13 +585,13 @@
 
         engine.clearAndReadState();
 
-        syncs = engine.getPeriodicSyncs(account, 0, authority1);
+        syncs = engine.getPeriodicSyncs(target1);
         assertEquals(0, syncs.size());
 
-        syncs = engine.getPeriodicSyncs(account, 0, authority2);
+        syncs = engine.getPeriodicSyncs(target2);
         assertEquals(0, syncs.size());
 
-        syncs = engine.getPeriodicSyncs(account, 0, authority3);
+        syncs = engine.getPeriodicSyncs(target3);
         assertEquals(0, syncs.size());
 
         accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
@@ -547,15 +614,15 @@
 
         engine.clearAndReadState();
 
-        syncs = engine.getPeriodicSyncs(account, 0, authority1);
+        syncs = engine.getPeriodicSyncs(target1);
         assertEquals(1, syncs.size());
         assertEquals(sync1s, syncs.get(0));
 
-        syncs = engine.getPeriodicSyncs(account, 0, authority2);
+        syncs = engine.getPeriodicSyncs(target2);
         assertEquals(1, syncs.size());
         assertEquals(sync2s, syncs.get(0));
 
-        syncs = engine.getPeriodicSyncs(account, 0, authority3);
+        syncs = engine.getPeriodicSyncs(target3);
         assertEquals(1, syncs.size());
         assertEquals(sync3s, syncs.get(0));
     }