Rewrite SyncStorageEngine to use flat files and in-memory data structures.

The previous implementation used a database for storing all of its state, which could cause
a significant amount of IO activity as its tables were updated through the stages of a sync.
This new implementation replaces that in-memory data structures, with hand-written code
for writing them to persistent storage.

There are now 4 files associated with this class, holding various pieces of its state that
should be consistent.  These are everything from a main XML file of account information that
must always be retained, to a binary file of per-day statistics that can be thrown away at
any time.  Writes of these files as scheduled at various times based on their importance of
the frequency at which they change.

Because the database no longer exists, there needs to be a new explicit interface for
interacting with the sync manager database.  This is provided by new APIs on IContentService,
with a hidden method on ContentResolver to retrieve the IContentService so that various
system entities can use it.  Other changes in other projects are required to update to the
new API.

The goal here is to have as little an impact on the code and functionality outside of
SyncStorageEngine, though due to the necessary change in API it is still somewhat extensive.
diff --git a/Android.mk b/Android.mk
index 42a6b31..ca76fb4 100644
--- a/Android.mk
+++ b/Android.mk
@@ -85,8 +85,10 @@
 	core/java/android/bluetooth/IBluetoothDevice.aidl \
 	core/java/android/bluetooth/IBluetoothDeviceCallback.aidl \
 	core/java/android/bluetooth/IBluetoothHeadset.aidl \
+    core/java/android/content/IContentService.aidl \
 	core/java/android/content/ISyncAdapter.aidl \
 	core/java/android/content/ISyncContext.aidl \
+    core/java/android/content/ISyncStatusObserver.aidl \
 	core/java/android/content/pm/IPackageDataObserver.aidl \
 	core/java/android/content/pm/IPackageDeleteObserver.aidl \
 	core/java/android/content/pm/IPackageInstallObserver.aidl \
diff --git a/api/current.xml b/api/current.xml
index f01ed14..2bab739 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -92268,6 +92268,19 @@
  visibility="public"
 >
 </method>
+<method name="getBroadcastCookie"
+ return="java.lang.Object"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="index" type="int">
+</parameter>
+</method>
 <method name="getBroadcastItem"
  return="E"
  abstract="false"
@@ -92305,6 +92318,21 @@
 <parameter name="callback" type="E">
 </parameter>
 </method>
+<method name="onCallbackDied"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="callback" type="E">
+</parameter>
+<parameter name="cookie" type="java.lang.Object">
+</parameter>
+</method>
 <method name="register"
  return="boolean"
  abstract="false"
@@ -92318,6 +92346,21 @@
 <parameter name="callback" type="E">
 </parameter>
 </method>
+<method name="register"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="callback" type="E">
+</parameter>
+<parameter name="cookie" type="java.lang.Object">
+</parameter>
+</method>
 <method name="unregister"
  return="boolean"
  abstract="false"
diff --git a/core/java/android/content/ActiveSyncInfo.aidl b/core/java/android/content/ActiveSyncInfo.aidl
new file mode 100644
index 0000000..1142206
--- /dev/null
+++ b/core/java/android/content/ActiveSyncInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+parcelable ActiveSyncInfo;
diff --git a/core/java/android/content/ActiveSyncInfo.java b/core/java/android/content/ActiveSyncInfo.java
new file mode 100644
index 0000000..63be8d1
--- /dev/null
+++ b/core/java/android/content/ActiveSyncInfo.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.os.Parcel;
+import android.os.Parcelable.Creator;
+
+/** @hide */
+public class ActiveSyncInfo {
+    public final int authorityId;
+    public final String account;
+    public final String authority;
+    public final long startTime;
+    
+    ActiveSyncInfo(int authorityId, String account, String authority,
+            long startTime) {
+        this.authorityId = authorityId;
+        this.account = account;
+        this.authority = authority;
+        this.startTime = startTime;
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(authorityId);
+        parcel.writeString(account);
+        parcel.writeString(authority);
+        parcel.writeLong(startTime);
+    }
+
+    ActiveSyncInfo(Parcel parcel) {
+        authorityId = parcel.readInt();
+        account = parcel.readString();
+        authority = parcel.readString();
+        startTime = parcel.readLong();
+    }
+    
+    public static final Creator<ActiveSyncInfo> CREATOR = new Creator<ActiveSyncInfo>() {
+        public ActiveSyncInfo createFromParcel(Parcel in) {
+            return new ActiveSyncInfo(in);
+        }
+
+        public ActiveSyncInfo[] newArray(int size) {
+            return new ActiveSyncInfo[size];
+        }
+    };
+}
\ No newline at end of file
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 0a71d57..74144fc 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -25,9 +25,13 @@
 import android.database.IContentObserver;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -85,8 +89,7 @@
      */
     public static final String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir";
     
-    public ContentResolver(Context context)
-    {
+    public ContentResolver(Context context) {
         mContext = context;
     }
 
@@ -605,7 +608,7 @@
             ContentObserver observer)
     {
         try {
-            ContentServiceNative.getDefault().registerContentObserver(uri, notifyForDescendents,
+            getContentService().registerContentObserver(uri, notifyForDescendents,
                     observer.getContentObserver());
         } catch (RemoteException e) {
         }
@@ -621,7 +624,7 @@
         try {
             IContentObserver contentObserver = observer.releaseContentObserver();
             if (contentObserver != null) {
-                ContentServiceNative.getDefault().unregisterContentObserver(
+                getContentService().unregisterContentObserver(
                         contentObserver);
             }
         } catch (RemoteException e) {
@@ -651,7 +654,7 @@
      */
     public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
         try {
-            ContentServiceNative.getDefault().notifyChange(
+            getContentService().notifyChange(
                     uri, observer == null ? null : observer.getContentObserver(),
                     observer != null && observer.deliverSelfNotifications(), syncToNetwork);
         } catch (RemoteException e) {
@@ -677,7 +680,7 @@
     public void startSync(Uri uri, Bundle extras) {
         validateSyncExtrasBundle(extras);
         try {
-            ContentServiceNative.getDefault().startSync(uri, extras);
+            getContentService().startSync(uri, extras);
         } catch (RemoteException e) {
         }
     }
@@ -718,7 +721,7 @@
 
     public void cancelSync(Uri uri) {
         try {
-            ContentServiceNative.getDefault().cancelSync(uri);
+            getContentService().cancelSync(uri);
         } catch (RemoteException e) {
         }
     }
@@ -779,6 +782,22 @@
         }
     }
 
+    /** @hide */
+    public static final String CONTENT_SERVICE_NAME = "content";
+    
+    /** @hide */
+    public static IContentService getContentService() {
+        if (sContentService != null) {
+            return sContentService;
+        }
+        IBinder b = ServiceManager.getService(CONTENT_SERVICE_NAME);
+        if (Config.LOGV) Log.v("ContentService", "default service binder = " + b);
+        sContentService = IContentService.Stub.asInterface(b);
+        if (Config.LOGV) Log.v("ContentService", "default service = " + sContentService);
+        return sContentService;
+    }
+    
+    private static IContentService sContentService;
     private final Context mContext;
     private static final String TAG = "ContentResolver";
 }
diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java
index b028868..6cd2c54 100644
--- a/core/java/android/content/ContentService.java
+++ b/core/java/android/content/ContentService.java
@@ -21,6 +21,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Config;
@@ -34,7 +35,7 @@
 /**
  * {@hide}
  */
-public final class ContentService extends ContentServiceNative {
+public final class ContentService extends IContentService.Stub {
     private static final String TAG = "ContentService";
     private Context mContext;
     private boolean mFactoryTest;
@@ -73,6 +74,21 @@
         }
     }
 
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        try {
+            return super.onTransact(code, data, reply, flags);
+        } catch (RuntimeException e) {
+            // The content service only throws security exceptions, so let's
+            // log all others.
+            if (!(e instanceof SecurityException)) {
+                Log.e(TAG, "Content Service Crash", e);
+            }
+            throw e;
+        }
+    }
+
     /*package*/ ContentService(Context context, boolean factoryTest) {
         mContext = context;
         mFactoryTest = factoryTest;
@@ -204,9 +220,158 @@
         }
     }
 
+    public boolean getSyncProviderAutomatically(String providerName) {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
+                "no permission to read the sync settings");
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                return syncManager.getSyncStorageEngine().getSyncProviderAutomatically(
+                        null, providerName);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+        return false;
+    }
+
+    public void setSyncProviderAutomatically(String providerName, boolean sync) {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+                "no permission to write the sync settings");
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                syncManager.getSyncStorageEngine().setSyncProviderAutomatically(
+                        null, providerName, sync);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    public boolean getListenForNetworkTickles() {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
+                "no permission to read the sync settings");
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                return syncManager.getSyncStorageEngine().getListenForNetworkTickles();
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+        return false;
+    }
+    
+    public void setListenForNetworkTickles(boolean flag) {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+                "no permission to write the sync settings");
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                syncManager.getSyncStorageEngine().setListenForNetworkTickles(flag);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    public boolean isSyncActive(String account, String authority) {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
+                "no permission to read the sync stats");
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                return syncManager.getSyncStorageEngine().isSyncActive(
+                        account, authority);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+        return false;
+    }
+    
+    public ActiveSyncInfo getActiveSync() {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
+                "no permission to read the sync stats");
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                return syncManager.getSyncStorageEngine().getActiveSync();
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+        return null;
+    }
+    
+    public SyncStatusInfo getStatusByAuthority(String authority) {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
+                "no permission to read the sync stats");
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                return syncManager.getSyncStorageEngine().getStatusByAuthority(
+                        authority);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+        return null;
+    }
+    
+    public boolean isAuthorityPending(String account, String authority) {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
+                "no permission to read the sync stats");
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                return syncManager.getSyncStorageEngine().isAuthorityPending(
+                        account, authority);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+        return false;
+    }
+    
+    public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                syncManager.getSyncStorageEngine().addStatusChangeListener(
+                        mask, callback);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+    
+    public void removeStatusChangeListener(ISyncStatusObserver callback) {
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                syncManager.getSyncStorageEngine().removeStatusChangeListener(
+                        callback);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+    
     public static IContentService main(Context context, boolean factoryTest) {
         ContentService service = new ContentService(context, factoryTest);
-        ServiceManager.addService("content", service);
+        ServiceManager.addService(ContentResolver.CONTENT_SERVICE_NAME, service);
         return service;
     }
 
diff --git a/core/java/android/content/ContentServiceNative.java b/core/java/android/content/ContentServiceNative.java
deleted file mode 100644
index 364f9ee..0000000
--- a/core/java/android/content/ContentServiceNative.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content;
-
-import android.database.IContentObserver;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.RemoteException;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.ServiceManager;
-import android.os.Bundle;
-import android.util.Config;
-import android.util.Log;
-
-/**
- * {@hide}
- */
-abstract class ContentServiceNative extends Binder implements IContentService
-{
-    public ContentServiceNative()
-    {
-        attachInterface(this, descriptor);
-    }
-
-    /**
-     * Cast a Binder object into a content resolver interface, generating
-     * a proxy if needed.
-     */
-    static public IContentService asInterface(IBinder obj)
-    {
-        if (obj == null) {
-            return null;
-        }
-        IContentService in =
-            (IContentService)obj.queryLocalInterface(descriptor);
-        if (in != null) {
-            return in;
-        }
-
-        return new ContentServiceProxy(obj);
-    }
-
-    /**
-     * Retrieve the system's default/global content service.
-     */
-    static public IContentService getDefault()
-    {
-        if (gDefault != null) {
-            return gDefault;
-        }
-        IBinder b = ServiceManager.getService("content");
-        if (Config.LOGV) Log.v("ContentService", "default service binder = " + b);
-        gDefault = asInterface(b);
-        if (Config.LOGV) Log.v("ContentService", "default service = " + gDefault);
-        return gDefault;
-    }
-
-    @Override
-    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
-    {
-        try {
-        switch (code) {
-            case 5038: {
-                data.readString(); // ignore the interface token that service generated
-                Uri uri = Uri.parse(data.readString());
-                notifyChange(uri, null, false, false);
-                return true;
-            }
-            
-            case REGISTER_CONTENT_OBSERVER_TRANSACTION: {
-                Uri uri = Uri.CREATOR.createFromParcel(data);
-                boolean notifyForDescendents = data.readInt() != 0;
-                IContentObserver observer = IContentObserver.Stub.asInterface(data.readStrongBinder());
-                registerContentObserver(uri, notifyForDescendents, observer);
-                return true;
-            }
-
-            case UNREGISTER_CHANGE_OBSERVER_TRANSACTION: {
-                IContentObserver observer = IContentObserver.Stub.asInterface(data.readStrongBinder());
-                unregisterContentObserver(observer);
-                return true;
-            }
-
-            case NOTIFY_CHANGE_TRANSACTION: {
-                Uri uri = Uri.CREATOR.createFromParcel(data);
-                IContentObserver observer = IContentObserver.Stub.asInterface(data.readStrongBinder());
-                boolean observerWantsSelfNotifications = data.readInt() != 0;
-                boolean syncToNetwork = data.readInt() != 0;
-                notifyChange(uri, observer, observerWantsSelfNotifications, syncToNetwork);
-                return true;
-            }
-
-            case START_SYNC_TRANSACTION: {
-                Uri url = null;
-                int hasUrl = data.readInt();
-                if (hasUrl != 0) {
-                    url = Uri.CREATOR.createFromParcel(data);
-                }
-                startSync(url, data.readBundle());
-                return true;
-            }
-
-            case CANCEL_SYNC_TRANSACTION: {
-                Uri url = null;
-                int hasUrl = data.readInt();
-                if (hasUrl != 0) {
-                    url = Uri.CREATOR.createFromParcel(data);
-                }
-                cancelSync(url);
-                return true;
-            }
-
-            default:
-                return super.onTransact(code, data, reply, flags);
-        }
-        } catch (Exception e) {
-            Log.e("ContentServiceNative", "Caught exception in transact", e);
-        }
-
-        return false;
-    }
-
-    public IBinder asBinder()
-    {
-        return this;
-    }
-
-    private static IContentService gDefault;
-}
-
-
-final class ContentServiceProxy implements IContentService
-{
-    public ContentServiceProxy(IBinder remote)
-    {
-        mRemote = remote;
-    }
-
-    public IBinder asBinder()
-    {
-        return mRemote;
-    }
-
-    public void registerContentObserver(Uri uri, boolean notifyForDescendents,
-            IContentObserver observer) throws RemoteException
-    {
-        Parcel data = Parcel.obtain();
-        uri.writeToParcel(data, 0);
-        data.writeInt(notifyForDescendents ? 1 : 0);
-        data.writeStrongInterface(observer);
-        mRemote.transact(REGISTER_CONTENT_OBSERVER_TRANSACTION, data, null, 0);
-        data.recycle();
-    }
-
-    public void unregisterContentObserver(IContentObserver observer) throws RemoteException {
-        Parcel data = Parcel.obtain();
-        data.writeStrongInterface(observer);
-        mRemote.transact(UNREGISTER_CHANGE_OBSERVER_TRANSACTION, data, null, 0);
-        data.recycle();
-    }
-
-    public void notifyChange(Uri uri, IContentObserver observer,
-            boolean observerWantsSelfNotifications, boolean syncToNetwork)
-            throws RemoteException {
-        Parcel data = Parcel.obtain();
-        uri.writeToParcel(data, 0);
-        data.writeStrongInterface(observer);
-        data.writeInt(observerWantsSelfNotifications ? 1 : 0);
-        data.writeInt(syncToNetwork ? 1 : 0);
-        mRemote.transact(NOTIFY_CHANGE_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
-        data.recycle();
-    }
-
-    public void startSync(Uri url, Bundle extras) throws RemoteException {
-        Parcel data = Parcel.obtain();
-        if (url == null) {
-            data.writeInt(0);
-        } else {
-            data.writeInt(1);
-            url.writeToParcel(data, 0);
-        }
-        extras.writeToParcel(data, 0);
-        mRemote.transact(START_SYNC_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
-        data.recycle();
-    }
-
-    public void cancelSync(Uri url) throws RemoteException {
-        Parcel data = Parcel.obtain();
-        if (url == null) {
-            data.writeInt(0);
-        } else {
-            data.writeInt(1);
-            url.writeToParcel(data, 0);
-        }
-        mRemote.transact(CANCEL_SYNC_TRANSACTION, data, null /* reply */, IBinder.FLAG_ONEWAY);
-        data.recycle();
-    }
-
-    private IBinder mRemote;
-}
-
diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl
new file mode 100644
index 0000000..8617d949
--- /dev/null
+++ b/core/java/android/content/IContentService.aidl
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.content.ActiveSyncInfo;
+import android.content.ISyncStatusObserver;
+import android.content.SyncStatusInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.database.IContentObserver;
+
+/**
+ * @hide
+ */
+interface IContentService {
+    void registerContentObserver(in Uri uri, boolean notifyForDescendentsn,
+            IContentObserver observer);
+    void unregisterContentObserver(IContentObserver observer);
+
+    void notifyChange(in Uri uri, IContentObserver observer,
+            boolean observerWantsSelfNotifications, boolean syncToNetwork);
+
+    void startSync(in Uri url, in Bundle extras);
+    void cancelSync(in Uri uri);
+    
+    /**
+     * Check if the provider should be synced when a network tickle is received
+     * @param providerName the provider whose setting we are querying
+     * @return true of the provider should be synced when a network tickle is received
+     */
+    boolean getSyncProviderAutomatically(String providerName);
+
+    /**
+     * Set whether or not the provider is synced when it receives a network tickle.
+     *
+     * @param providerName the provider whose behavior is being controlled
+     * @param sync true if the provider should be synced when tickles are received for it
+     */
+    void setSyncProviderAutomatically(String providerName, boolean sync);
+
+    void setListenForNetworkTickles(boolean flag);
+
+    boolean getListenForNetworkTickles();
+    
+    /**
+     * 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(String account, String authority);
+    
+    ActiveSyncInfo getActiveSync();
+    
+    /**
+     * 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
+     */
+    SyncStatusInfo getStatusByAuthority(String authority);
+
+    /**
+     * Return true if the pending status is true of any matching authorities.
+     */
+    boolean isAuthorityPending(String account, String authority);
+    
+    void addStatusChangeListener(int mask, ISyncStatusObserver callback);
+    
+    void removeStatusChangeListener(ISyncStatusObserver callback);
+}
diff --git a/core/java/android/content/IContentService.java b/core/java/android/content/IContentService.java
deleted file mode 100644
index a3047da..0000000
--- a/core/java/android/content/IContentService.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content;
-
-import android.database.IContentObserver;
-import android.net.Uri;
-import android.os.RemoteException;
-import android.os.IBinder;
-import android.os.IInterface;
-import android.os.Bundle;
-
-/**
- * {@hide}
- */
-public interface IContentService extends IInterface
-{
-    public void registerContentObserver(Uri uri, boolean notifyForDescendentsn,
-            IContentObserver observer) throws RemoteException;
-    public void unregisterContentObserver(IContentObserver observer) throws RemoteException;
-
-    public void notifyChange(Uri uri, IContentObserver observer,
-            boolean observerWantsSelfNotifications, boolean syncToNetwork)
-            throws RemoteException;
-
-    public void startSync(Uri url, Bundle extras) throws RemoteException;
-    public void cancelSync(Uri uri) throws RemoteException;
-
-    static final String SERVICE_NAME = "content";
-
-    /* IPC constants */
-    static final String descriptor = "android.content.IContentService";
-
-    static final int REGISTER_CONTENT_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 1;
-    static final int UNREGISTER_CHANGE_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 2;
-    static final int NOTIFY_CHANGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 3;
-    static final int START_SYNC_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 4;
-    static final int CANCEL_SYNC_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 5;
-}
-
diff --git a/core/java/android/content/ISyncStatusObserver.aidl b/core/java/android/content/ISyncStatusObserver.aidl
new file mode 100644
index 0000000..eb26845
--- /dev/null
+++ b/core/java/android/content/ISyncStatusObserver.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+/**
+ * @hide
+ */
+oneway interface ISyncStatusObserver {
+    void onStatusChanged(int which);
+}
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 96470c3..ade5f3d 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -32,8 +32,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
-import android.database.Cursor;
-import android.database.DatabaseUtils;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.net.Uri;
@@ -43,18 +41,13 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Parcel;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
-import android.preference.Preference;
-import android.preference.PreferenceGroup;
-import android.provider.Sync;
 import android.provider.Settings;
-import android.provider.Sync.History;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.text.format.Time;
@@ -73,13 +66,12 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.PriorityQueue;
 import java.util.Random;
-import java.util.Observer;
-import java.util.Observable;
 
 /**
  * @hide
@@ -138,7 +130,6 @@
     volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
     volatile private boolean mDataConnectionIsConnected = false;
     volatile private boolean mStorageIsLow = false;
-    private Sync.Settings.QueryMap mSyncSettings;
 
     private final NotificationManager mNotificationMgr;
     private AlarmManager mAlarmService = null;
@@ -225,17 +216,6 @@
     private static final String SYNC_POLL_ALARM = "android.content.syncmanager.SYNC_POLL_ALARM";
     private final SyncHandler mSyncHandler;
 
-    private static final String[] SYNC_ACTIVE_PROJECTION = new String[]{
-            Sync.Active.ACCOUNT,
-            Sync.Active.AUTHORITY,
-            Sync.Active.START_TIME,
-    };
-
-    private static final String[] SYNC_PENDING_PROJECTION = new String[]{
-            Sync.Pending.ACCOUNT,
-            Sync.Pending.AUTHORITY
-    };
-
     private static final int MAX_SYNC_POLL_DELAY_SECONDS = 36 * 60 * 60; // 36 hours
     private static final int MIN_SYNC_POLL_DELAY_SECONDS = 24 * 60 * 60; // 24 hours
 
@@ -289,6 +269,14 @@
                 HANDLE_SYNC_ALARM_WAKE_LOCK);
         mHandleAlarmWakeLock.setReferenceCounted(false);
 
+        mSyncStorageEngine.addStatusChangeListener(
+                SyncStorageEngine.CHANGE_SETTINGS, new ISyncStatusObserver.Stub() {
+            public void onStatusChanged(int which) {
+                // force the sync loop to run if the settings change
+                sendCheckAlarmsMessage();
+            }
+        });
+        
         if (!factoryTest) {
             AccountMonitorListener listener = new AccountMonitorListener() {
                 public void onAccountsUpdated(String[] accounts) {
@@ -448,20 +436,10 @@
         return mActiveSyncContext;
     }
 
-    private Sync.Settings.QueryMap getSyncSettings() {
-        if (mSyncSettings == null) {
-            mSyncSettings = new Sync.Settings.QueryMap(mContext.getContentResolver(), true,
-                    new Handler());
-            mSyncSettings.addObserver(new Observer(){
-                public void update(Observable o, Object arg) {
-                    // force the sync loop to run if the settings change
-                    sendCheckAlarmsMessage();
-                }
-            });
-        }
-        return mSyncSettings;
+    public SyncStorageEngine getSyncStorageEngine() {
+        return mSyncStorageEngine;
     }
-
+    
     private void ensureContentResolver() {
         if (mContentResolver == null) {
             mContentResolver = mContext.getContentResolver();
@@ -574,15 +552,15 @@
 
         int source;
         if (uploadOnly) {
-            source = Sync.History.SOURCE_LOCAL;
+            source = SyncStorageEngine.SOURCE_LOCAL;
         } else if (force) {
-            source = Sync.History.SOURCE_USER;
+            source = SyncStorageEngine.SOURCE_USER;
         } else if (url == null) {
-            source = Sync.History.SOURCE_POLL;
+            source = SyncStorageEngine.SOURCE_POLL;
         } else {
             // this isn't strictly server, since arbitrary callers can (and do) request
             // a non-forced two-way sync on a specific url
-            source = Sync.History.SOURCE_SERVER;
+            source = SyncStorageEngine.SOURCE_SERVER;
         }
 
         List<String> names = new ArrayList<String>();
@@ -667,9 +645,7 @@
 
     public void updateHeartbeatTime() {
         mHeartbeatTime = SystemClock.elapsedRealtime();
-        ensureContentResolver();
-        mContentResolver.notifyChange(Sync.Active.CONTENT_URI,
-                null /* this change wasn't made through an observer */);
+        mSyncStorageEngine.reportActiveChange();
     }
 
     private void sendSyncAlarmMessage() {
@@ -876,7 +852,7 @@
         final String key;
         long earliestRunTime;
         long delay;
-        Long rowId = null;
+        SyncStorageEngine.PendingOperation pendingOperation;
 
         SyncOperation(String account, int source, String authority, Bundle extras, long delay) {
             this.account = account;
@@ -916,7 +892,7 @@
             sb.append(" when: ").append(earliestRunTime);
             sb.append(" delay: ").append(delay);
             sb.append(" key: {").append(key).append("}");
-            if (rowId != null) sb.append(" rowId: ").append(rowId);
+            if (pendingOperation != null) sb.append(" pendingOperation: ").append(pendingOperation);
             return sb.toString();
         }
 
@@ -999,242 +975,262 @@
 
     protected void dump(FileDescriptor fd, PrintWriter pw) {
         StringBuilder sb = new StringBuilder();
-        dumpSyncState(sb);
-        sb.append("\n");
+        dumpSyncState(pw, sb);
         if (isSyncEnabled()) {
-            dumpSyncHistory(sb);
+            dumpSyncHistory(pw, sb);
         }
-        pw.println(sb.toString());
     }
 
-    protected void dumpSyncState(StringBuilder sb) {
-        sb.append("sync enabled: ").append(isSyncEnabled()).append("\n");
-        sb.append("data connected: ").append(mDataConnectionIsConnected).append("\n");
-        sb.append("memory low: ").append(mStorageIsLow).append("\n");
+    static String formatTime(long time) {
+        Time tobj = new Time();
+        tobj.set(time);
+        return tobj.format("%Y-%m-%d %H:%M:%S");
+    }
+    
+    protected void dumpSyncState(PrintWriter pw, StringBuilder sb) {
+        pw.print("sync enabled: "); pw.println(isSyncEnabled());
+        pw.print("data connected: "); pw.println(mDataConnectionIsConnected);
+        pw.print("memory low: "); pw.println(mStorageIsLow);
 
         final String[] accounts = mAccounts;
-        sb.append("accounts: ");
+        pw.print("accounts: ");
         if (accounts != null) {
-            sb.append(accounts.length);
+            pw.println(accounts.length);
         } else {
-            sb.append("none");
+            pw.println("none");
         }
-        sb.append("\n");
         final long now = SystemClock.elapsedRealtime();
-        sb.append("now: ").append(now).append("\n");
-        sb.append("uptime: ").append(DateUtils.formatElapsedTime(now/1000)).append(" (HH:MM:SS)\n");
-        sb.append("time spent syncing : ")
-                .append(DateUtils.formatElapsedTime(
-                        mSyncHandler.mSyncTimeTracker.timeSpentSyncing() / 1000))
-                .append(" (HH:MM:SS), sync ")
-                .append(mSyncHandler.mSyncTimeTracker.mLastWasSyncing ? "" : "not ")
-                .append("in progress").append("\n");
+        pw.print("now: "); pw.println(now);
+        pw.print("uptime: "); pw.print(DateUtils.formatElapsedTime(now/1000));
+                pw.println(" (HH:MM:SS)");
+        pw.print("time spent syncing: ");
+                pw.print(DateUtils.formatElapsedTime(
+                        mSyncHandler.mSyncTimeTracker.timeSpentSyncing() / 1000));
+                pw.print(" (HH:MM:SS), sync ");
+                pw.print(mSyncHandler.mSyncTimeTracker.mLastWasSyncing ? "" : "not ");
+                pw.println("in progress");
         if (mSyncHandler.mAlarmScheduleTime != null) {
-            sb.append("next alarm time: ").append(mSyncHandler.mAlarmScheduleTime)
-                    .append(" (")
-                    .append(DateUtils.formatElapsedTime((mSyncHandler.mAlarmScheduleTime-now)/1000))
-                    .append(" (HH:MM:SS) from now)\n");
+            pw.print("next alarm time: "); pw.print(mSyncHandler.mAlarmScheduleTime);
+                    pw.print(" (");
+                    pw.print(DateUtils.formatElapsedTime((mSyncHandler.mAlarmScheduleTime-now)/1000));
+                    pw.println(" (HH:MM:SS) from now)");
         } else {
-            sb.append("no alarm is scheduled (there had better not be any pending syncs)\n");
+            pw.println("no alarm is scheduled (there had better not be any pending syncs)");
         }
 
-        sb.append("active sync: ").append(mActiveSyncContext).append("\n");
+        pw.print("active sync: "); pw.println(mActiveSyncContext);
 
-        sb.append("notification info: ");
+        pw.print("notification info: ");
+        sb.setLength(0);
         mSyncHandler.mSyncNotificationInfo.toString(sb);
-        sb.append("\n");
+        pw.println(sb.toString());
 
         synchronized (mSyncQueue) {
-            sb.append("sync queue: ");
+            pw.print("sync queue: ");
+            sb.setLength(0);
             mSyncQueue.dump(sb);
+            pw.println(sb.toString());
         }
 
-        Cursor c = mSyncStorageEngine.query(Sync.Active.CONTENT_URI,
-                SYNC_ACTIVE_PROJECTION, null, null, null);
-        sb.append("\n");
-        try {
-            if (c.moveToNext()) {
-                final long durationInSeconds = (now - c.getLong(2)) / 1000;
-                sb.append("Active sync: ").append(c.getString(0))
-                        .append(" ").append(c.getString(1))
-                        .append(", duration is ")
-                        .append(DateUtils.formatElapsedTime(durationInSeconds)).append(".\n");
-            } else {
-                sb.append("No sync is in progress.\n");
-            }
-        } finally {
-            c.close();
-        }
-
-        c = mSyncStorageEngine.query(Sync.Pending.CONTENT_URI,
-                SYNC_PENDING_PROJECTION, null, null, "account, authority");
-        sb.append("\nPending Syncs\n");
-        try {
-            if (c.getCount() != 0) {
-                dumpSyncPendingHeader(sb);
-                while (c.moveToNext()) {
-                    dumpSyncPendingRow(sb, c);
-                }
-                dumpSyncPendingFooter(sb);
-            } else {
-                sb.append("none\n");
-            }
-        } finally {
-            c.close();
-        }
-
-        String currentAccount = null;
-        c = mSyncStorageEngine.query(Sync.Status.CONTENT_URI,
-                STATUS_PROJECTION, null, null, "account, authority");
-        sb.append("\nSync history by account and authority\n");
-        try {
-            while (c.moveToNext()) {
-                if (!TextUtils.equals(currentAccount, c.getString(0))) {
-                    if (currentAccount != null) {
-                        dumpSyncHistoryFooter(sb);
-                    }
-                    currentAccount = c.getString(0);
-                    dumpSyncHistoryHeader(sb, currentAccount);
-                }
-
-                dumpSyncHistoryRow(sb, c);
-            }
-            if (c.getCount() > 0) dumpSyncHistoryFooter(sb);
-        } finally {
-            c.close();
-        }
-    }
-
-    private void dumpSyncHistoryHeader(StringBuilder sb, String account) {
-        sb.append(" Account: ").append(account).append("\n");
-        sb.append("  ___________________________________________________________________________________________________________________________\n");
-        sb.append(" |                 |             num times synced           |   total  |         last success          |                     |\n");
-        sb.append(" | authority       | local |  poll | server |  user | total | duration |  source |               time  |   result if failing |\n");
-    }
-
-    private static String[] STATUS_PROJECTION = new String[]{
-            Sync.Status.ACCOUNT, // 0
-            Sync.Status.AUTHORITY, // 1
-            Sync.Status.NUM_SYNCS, // 2
-            Sync.Status.TOTAL_ELAPSED_TIME, // 3
-            Sync.Status.NUM_SOURCE_LOCAL, // 4
-            Sync.Status.NUM_SOURCE_POLL, // 5
-            Sync.Status.NUM_SOURCE_SERVER, // 6
-            Sync.Status.NUM_SOURCE_USER, // 7
-            Sync.Status.LAST_SUCCESS_SOURCE, // 8
-            Sync.Status.LAST_SUCCESS_TIME, // 9
-            Sync.Status.LAST_FAILURE_SOURCE, // 10
-            Sync.Status.LAST_FAILURE_TIME, // 11
-            Sync.Status.LAST_FAILURE_MESG // 12
-    };
-
-    private void dumpSyncHistoryRow(StringBuilder sb, Cursor c) {
-        boolean hasSuccess = !c.isNull(9);
-        boolean hasFailure = !c.isNull(11);
-        Time timeSuccess = new Time();
-        if (hasSuccess) timeSuccess.set(c.getLong(9));
-        Time timeFailure = new Time();
-        if (hasFailure) timeFailure.set(c.getLong(11));
-        sb.append(String.format(" | %-15s | %5d | %5d | %6d | %5d | %5d | %8s | %7s | %19s | %19s |\n",
-                c.getString(1),
-                c.getLong(4),
-                c.getLong(5),
-                c.getLong(6),
-                c.getLong(7),
-                c.getLong(2),
-                DateUtils.formatElapsedTime(c.getLong(3)/1000),
-                hasSuccess ? Sync.History.SOURCES[c.getInt(8)] : "",
-                hasSuccess ? timeSuccess.format("%Y-%m-%d %H:%M:%S") : "",
-                hasFailure ? History.mesgToString(c.getString(12)) : ""));
-    }
-
-    private void dumpSyncHistoryFooter(StringBuilder sb) {
-        sb.append(" |___________________________________________________________________________________________________________________________|\n");
-    }
-
-    private void dumpSyncPendingHeader(StringBuilder sb) {
-        sb.append(" ____________________________________________________\n");
-        sb.append(" | account                        | authority       |\n");
-    }
-
-    private void dumpSyncPendingRow(StringBuilder sb, Cursor c) {
-        sb.append(String.format(" | %-30s | %-15s |\n", c.getString(0), c.getString(1)));
-    }
-
-    private void dumpSyncPendingFooter(StringBuilder sb) {
-        sb.append(" |__________________________________________________|\n");
-    }
-
-    protected void dumpSyncHistory(StringBuilder sb) {
-        Cursor c = mSyncStorageEngine.query(Sync.History.CONTENT_URI, null, "event=?",
-                new String[]{String.valueOf(Sync.History.EVENT_STOP)},
-                Sync.HistoryColumns.EVENT_TIME + " desc");
-        try {
-            long numSyncsLastHour = 0, durationLastHour = 0;
-            long numSyncsLastDay = 0, durationLastDay = 0;
-            long numSyncsLastWeek = 0, durationLastWeek = 0;
-            long numSyncsLast4Weeks = 0, durationLast4Weeks = 0;
-            long numSyncsTotal = 0, durationTotal = 0;
-
-            long now = System.currentTimeMillis();
-            int indexEventTime = c.getColumnIndexOrThrow(Sync.History.EVENT_TIME);
-            int indexElapsedTime = c.getColumnIndexOrThrow(Sync.History.ELAPSED_TIME);
-            while (c.moveToNext()) {
-                long duration = c.getLong(indexElapsedTime);
-                long endTime = c.getLong(indexEventTime) + duration;
-                long millisSinceStart = now - endTime;
-                numSyncsTotal++;
-                durationTotal += duration;
-                if (millisSinceStart < MILLIS_IN_HOUR) {
-                    numSyncsLastHour++;
-                    durationLastHour += duration;
-                }
-                if (millisSinceStart < MILLIS_IN_DAY) {
-                    numSyncsLastDay++;
-                    durationLastDay += duration;
-                }
-                if (millisSinceStart < MILLIS_IN_WEEK) {
-                    numSyncsLastWeek++;
-                    durationLastWeek += duration;
-                }
-                if (millisSinceStart < MILLIS_IN_4WEEKS) {
-                    numSyncsLast4Weeks++;
-                    durationLast4Weeks += duration;
-                }
-            }
-            dumpSyncIntervalHeader(sb);
-            dumpSyncInterval(sb, "hour", MILLIS_IN_HOUR, numSyncsLastHour, durationLastHour);
-            dumpSyncInterval(sb, "day", MILLIS_IN_DAY, numSyncsLastDay, durationLastDay);
-            dumpSyncInterval(sb, "week", MILLIS_IN_WEEK, numSyncsLastWeek, durationLastWeek);
-            dumpSyncInterval(sb, "4 weeks",
-                    MILLIS_IN_4WEEKS, numSyncsLast4Weeks, durationLast4Weeks);
-            dumpSyncInterval(sb, "total", 0, numSyncsTotal, durationTotal);
-            dumpSyncIntervalFooter(sb);
-        } finally {
-            c.close();
-        }
-    }
-
-    private void dumpSyncIntervalHeader(StringBuilder sb) {
-        sb.append("Sync Stats\n");
-        sb.append(" ___________________________________________________________\n");
-        sb.append(" |          |        |   duration in sec   |               |\n");
-        sb.append(" | interval |  count |  average |    total | % of interval |\n");
-    }
-
-    private void dumpSyncInterval(StringBuilder sb, String label,
-            long interval, long numSyncs, long duration) {
-        sb.append(String.format(" | %-8s | %6d | %8.1f | %8.1f",
-                label, numSyncs, ((float)duration/numSyncs)/1000, (float)duration/1000));
-        if (interval > 0) {
-            sb.append(String.format(" | %13.2f |\n", ((float)duration/interval)*100.0));
+        ActiveSyncInfo active = mSyncStorageEngine.getActiveSync();
+        if (active != null) {
+            SyncStorageEngine.AuthorityInfo authority
+                    = mSyncStorageEngine.getAuthority(active.authorityId);
+            final long durationInSeconds = (now - active.startTime) / 1000;
+            pw.print("Active sync: ");
+                    pw.print(authority != null ? authority.account : "<no account>");
+                    pw.print(" ");
+                    pw.print(authority != null ? authority.authority : "<no account>");
+                    pw.print(", duration is ");
+                    pw.println(DateUtils.formatElapsedTime(durationInSeconds));
         } else {
-            sb.append(String.format(" | %13s |\n", "na"));
+            pw.println("No sync is in progress.");
+        }
+
+        ArrayList<SyncStorageEngine.PendingOperation> ops
+                = mSyncStorageEngine.getPendingOperations();
+        if (ops != null && ops.size() > 0) {
+            pw.println();
+            pw.println("Pending Syncs");
+            final int N = ops.size();
+            for (int i=0; i<N; i++) {
+                SyncStorageEngine.PendingOperation op = ops.get(i);
+                pw.print("  #"); pw.print(i); pw.print(": account=");
+                pw.print(op.account); pw.print(" authority=");
+                pw.println(op.authority);
+                if (op.extras != null && op.extras.size() > 0) {
+                    sb.setLength(0);
+                    SyncOperation.extrasToStringBuilder(op.extras, sb);
+                    pw.print("    extras: "); pw.println(sb.toString());
+                }
+            }
+        }
+
+        HashSet<String> processedAccounts = new HashSet<String>();
+        ArrayList<SyncStatusInfo> statuses
+                = mSyncStorageEngine.getSyncStatus();
+        if (statuses != null && statuses.size() > 0) {
+            pw.println();
+            pw.println("Sync Status");
+            final int N = statuses.size();
+            for (int i=0; i<N; i++) {
+                SyncStatusInfo status = statuses.get(i);
+                SyncStorageEngine.AuthorityInfo authority
+                        = mSyncStorageEngine.getAuthority(status.authorityId);
+                if (authority != null) {
+                    String curAccount = authority.account;
+                    
+                    if (processedAccounts.contains(curAccount)) {
+                        continue;
+                    }
+                    
+                    processedAccounts.add(curAccount);
+                    
+                    pw.print("  Account "); pw.print(authority.account);
+                    pw.println(":");
+                    for (int j=i; j<N; j++) {
+                        status = statuses.get(j);
+                        authority = mSyncStorageEngine.getAuthority(status.authorityId);
+                        if (!curAccount.equals(authority.account)) {
+                            continue;
+                        }
+                        pw.print("    "); pw.print(authority.authority);
+                        pw.println(":");
+                        pw.print("      count: local="); pw.print(status.numSourceLocal);
+                                pw.print(" poll="); pw.print(status.numSourcePoll);
+                                pw.print(" server="); pw.print(status.numSourceServer);
+                                pw.print(" user="); pw.print(status.numSourceUser);
+                                pw.print(" total="); pw.println(status.numSyncs);
+                        pw.print("      total duration: ");
+                                pw.println(DateUtils.formatElapsedTime(
+                                        status.totalElapsedTime/1000));
+                        if (status.lastSuccessTime != 0) {
+                            pw.print("      SUCCESS: source=");
+                                    pw.print(SyncStorageEngine.SOURCES[
+                                            status.lastSuccessSource]);
+                                    pw.print(" time=");
+                                    pw.println(formatTime(status.lastSuccessTime));
+                        } else {
+                            pw.print("      FAILURE: source=");
+                                    pw.print(SyncStorageEngine.SOURCES[
+                                            status.lastFailureSource]);
+                                    pw.print(" initialTime=");
+                                    pw.print(formatTime(status.initialFailureTime));
+                                    pw.print(" lastTime=");
+                                    pw.println(formatTime(status.lastFailureTime));
+                            pw.print("      message: "); pw.println(status.lastFailureMesg);
+                        }
+                    }
+                }
+            }
         }
     }
 
-    private void dumpSyncIntervalFooter(StringBuilder sb) {
-        sb.append(" |_________________________________________________________|\n");
+    private void dumpTimeSec(PrintWriter pw, long time) {
+        pw.print(time/1000); pw.print('.'); pw.print((time/100)%10);
+        pw.print('s');
+    }
+    
+    private void dumpDayStatistic(PrintWriter pw, SyncStorageEngine.DayStats ds) {
+        pw.print("Success ("); pw.print(ds.successCount);
+        if (ds.successCount > 0) {
+            pw.print(" for "); dumpTimeSec(pw, ds.successTime);
+            pw.print(" avg="); dumpTimeSec(pw, ds.successTime/ds.successCount);
+        }
+        pw.print(") Failure ("); pw.print(ds.failureCount);
+        if (ds.failureCount > 0) {
+            pw.print(" for "); dumpTimeSec(pw, ds.failureTime);
+            pw.print(" avg="); dumpTimeSec(pw, ds.failureTime/ds.failureCount);
+        }
+        pw.println(")");
+    }
+    
+    protected void dumpSyncHistory(PrintWriter pw, StringBuilder sb) {
+        SyncStorageEngine.DayStats dses[] = mSyncStorageEngine.getDayStatistics();
+        if (dses != null && dses[0] != null) {
+            pw.println();
+            pw.println("Sync Statistics");
+            pw.print("  Today:  "); dumpDayStatistic(pw, dses[0]);
+            int today = dses[0].day;
+            int i;
+            SyncStorageEngine.DayStats ds;
+            
+            // Print each day in the current week.
+            for (i=1; i<=6 && i < dses.length; i++) {
+                ds = dses[i];
+                if (ds == null) break;
+                int delta = today-ds.day;
+                if (delta > 6) break;
+                
+                pw.print("  Day-"); pw.print(delta); pw.print(":  ");
+                dumpDayStatistic(pw, ds);
+            }
+            
+            // Aggregate all following days into weeks and print totals.
+            int weekDay = today;
+            while (i < dses.length) {
+                SyncStorageEngine.DayStats aggr = null;
+                weekDay -= 7;
+                while (i < dses.length) {
+                    ds = dses[i];
+                    if (ds == null) {
+                        i = dses.length;
+                        break;
+                    }
+                    int delta = weekDay-ds.day;
+                    if (delta > 6) break;
+                    i++;
+                    
+                    if (aggr == null) {
+                        aggr = new SyncStorageEngine.DayStats(weekDay);
+                    }
+                    aggr.successCount += ds.successCount;
+                    aggr.successTime += ds.successTime;
+                    aggr.failureCount += ds.failureCount;
+                    aggr.failureTime += ds.failureTime;
+                }
+                if (aggr != null) {
+                    pw.print("  Week-"); pw.print((today-weekDay)/7); pw.print(": ");
+                    dumpDayStatistic(pw, aggr);
+                }
+            }
+        }
+        
+        ArrayList<SyncStorageEngine.SyncHistoryItem> items
+                = mSyncStorageEngine.getSyncHistory();
+        if (items != null && items.size() > 0) {
+            pw.println();
+            pw.println("Recent Sync History");
+            final int N = items.size();
+            for (int i=0; i<N; i++) {
+                SyncStorageEngine.SyncHistoryItem item = items.get(i);
+                SyncStorageEngine.AuthorityInfo authority
+                        = mSyncStorageEngine.getAuthority(item.authorityId);
+                pw.print("  #"); pw.print(i+1); pw.print(": ");
+                        pw.print(authority != null ? authority.account : "<no account>");
+                        pw.print(" ");
+                        pw.print(authority != null ? authority.authority : "<no account>");
+                Time time = new Time();
+                time.set(item.eventTime);
+                pw.print(" "); pw.print(SyncStorageEngine.SOURCES[item.source]);
+                        pw.print(" @ ");
+                        pw.print(formatTime(item.eventTime));
+                        pw.print(" for ");
+                        dumpTimeSec(pw, item.elapsedTime);
+                        pw.println();
+                if (item.event != SyncStorageEngine.EVENT_STOP
+                        || item.upstreamActivity !=0
+                        || item.downstreamActivity != 0) {
+                    pw.print("    event="); pw.print(item.event);
+                            pw.print(" upstreamActivity="); pw.print(item.upstreamActivity);
+                            pw.print(" downstreamActivity="); pw.println(item.downstreamActivity);
+                }
+                if (item.mesg != null
+                        && !SyncStorageEngine.MESG_SUCCESS.equals(item.mesg)) {
+                    pw.print("    mesg="); pw.println(item.mesg);
+                }
+            }
+        }
     }
 
     /**
@@ -1461,7 +1457,6 @@
             // found that is runnable (not disabled, etc). If that one is ready to run then
             // start it, otherwise just get out.
             SyncOperation syncOperation;
-            final Sync.Settings.QueryMap syncSettings = getSyncSettings();
             final ConnectivityManager connManager = (ConnectivityManager)
                     mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
             final boolean backgroundDataSetting = connManager.getBackgroundDataSetting();
@@ -1488,9 +1483,9 @@
                     final boolean force = syncOperation.extras.getBoolean(
                             ContentResolver.SYNC_EXTRAS_FORCE, false);
                     if (!force && (!backgroundDataSetting
-                            || !syncSettings.getListenForNetworkTickles()
-                            || !syncSettings.getSyncProviderAutomatically(
-                                    syncOperation.authority))) {
+                            || !mSyncStorageEngine.getListenForNetworkTickles()
+                            || !mSyncStorageEngine.getSyncProviderAutomatically(
+                                    null, syncOperation.authority))) {
                         if (isLoggable) {
                             Log.v(TAG, "runStateIdle: sync off, dropping " + syncOperation);
                         }
@@ -1616,7 +1611,7 @@
                     if (isLoggable) {
                         Log.v(TAG, "finished sync operation " + syncOperation);
                     }
-                    historyMessage = History.MESG_SUCCESS;
+                    historyMessage = SyncStorageEngine.MESG_SUCCESS;
                     // TODO: set these correctly when the SyncResult is extended to include it
                     downstreamActivity = 0;
                     upstreamActivity = 0;
@@ -1640,7 +1635,7 @@
                 } catch (RemoteException e) {
                     // we don't need to retry this in this case
                 }
-                historyMessage = History.MESG_CANCELED;
+                historyMessage = SyncStorageEngine.MESG_CANCELED;
                 downstreamActivity = 0;
                 upstreamActivity = 0;
             }
@@ -1675,14 +1670,22 @@
          *   If SyncResult.error() is true then it is safe to call this.
          */
         private int syncResultToErrorNumber(SyncResult syncResult) {
-            if (syncResult.syncAlreadyInProgress) return History.ERROR_SYNC_ALREADY_IN_PROGRESS;
-            if (syncResult.stats.numAuthExceptions > 0) return History.ERROR_AUTHENTICATION;
-            if (syncResult.stats.numIoExceptions > 0) return History.ERROR_IO;
-            if (syncResult.stats.numParseExceptions > 0) return History.ERROR_PARSE;
-            if (syncResult.stats.numConflictDetectedExceptions > 0) return History.ERROR_CONFLICT;
-            if (syncResult.tooManyDeletions) return History.ERROR_TOO_MANY_DELETIONS;
-            if (syncResult.tooManyRetries) return History.ERROR_TOO_MANY_RETRIES;
-            if (syncResult.databaseError) return History.ERROR_INTERNAL;
+            if (syncResult.syncAlreadyInProgress)
+                return SyncStorageEngine.ERROR_SYNC_ALREADY_IN_PROGRESS;
+            if (syncResult.stats.numAuthExceptions > 0)
+                return SyncStorageEngine.ERROR_AUTHENTICATION;
+            if (syncResult.stats.numIoExceptions > 0)
+                return SyncStorageEngine.ERROR_IO;
+            if (syncResult.stats.numParseExceptions > 0)
+                return SyncStorageEngine.ERROR_PARSE;
+            if (syncResult.stats.numConflictDetectedExceptions > 0)
+                return SyncStorageEngine.ERROR_CONFLICT;
+            if (syncResult.tooManyDeletions)
+                return SyncStorageEngine.ERROR_TOO_MANY_DELETIONS;
+            if (syncResult.tooManyRetries)
+                return SyncStorageEngine.ERROR_TOO_MANY_RETRIES;
+            if (syncResult.databaseError)
+                return SyncStorageEngine.ERROR_INTERNAL;
             throw new IllegalStateException("we are not in an error state, " + syncResult);
         }
 
@@ -1904,7 +1907,8 @@
             final int source = syncOperation.syncSource;
             final long now = System.currentTimeMillis();
 
-            EventLog.writeEvent(2720, syncOperation.authority, Sync.History.EVENT_START, source);
+            EventLog.writeEvent(2720, syncOperation.authority,
+                    SyncStorageEngine.EVENT_START, source);
 
             return mSyncStorageEngine.insertStartSyncEvent(
                     syncOperation.account, syncOperation.authority, now, source);
@@ -1912,7 +1916,8 @@
 
         public void stopSyncEvent(long rowId, SyncOperation syncOperation, String resultMessage,
                 int upstreamActivity, int downstreamActivity, long elapsedTime) {
-            EventLog.writeEvent(2720, syncOperation.authority, Sync.History.EVENT_STOP, syncOperation.syncSource);
+            EventLog.writeEvent(2720, syncOperation.authority,
+                    SyncStorageEngine.EVENT_STOP, syncOperation.syncSource);
 
             mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime, resultMessage,
                     downstreamActivity, upstreamActivity);
@@ -1921,18 +1926,6 @@
 
     static class SyncQueue {
         private SyncStorageEngine mSyncStorageEngine;
-        private final String[] COLUMNS = new String[]{
-                "_id",
-                "authority",
-                "account",
-                "extras",
-                "source"
-        };
-        private static final int COLUMN_ID = 0;
-        private static final int COLUMN_AUTHORITY = 1;
-        private static final int COLUMN_ACCOUNT = 2;
-        private static final int COLUMN_EXTRAS = 3;
-        private static final int COLUMN_SOURCE = 4;
 
         private static final boolean DEBUG_CHECK_DATA_CONSISTENCY = false;
 
@@ -1946,25 +1939,28 @@
 
         public SyncQueue(SyncStorageEngine syncStorageEngine) {
             mSyncStorageEngine = syncStorageEngine;
-            Cursor cursor = mSyncStorageEngine.getPendingSyncsCursor(COLUMNS);
-            try {
-                while (cursor.moveToNext()) {
-                    add(cursorToOperation(cursor),
-                            true /* this is being added from the database */);
-                }
-            } finally {
-                cursor.close();
-                if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
+            ArrayList<SyncStorageEngine.PendingOperation> ops
+                    = mSyncStorageEngine.getPendingOperations();
+            final int N = ops.size();
+            for (int i=0; i<N; i++) {
+                SyncStorageEngine.PendingOperation op = ops.get(i);
+                SyncOperation syncOperation = new SyncOperation(
+                        op.account, op.syncSource, op.authority, op.extras, 0);
+                syncOperation.pendingOperation = op;
+                add(syncOperation, op);
             }
+            
+            if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
         }
 
         public boolean add(SyncOperation operation) {
             return add(new SyncOperation(operation),
-                    false /* this is not coming from the database */);
+                    null /* this is not coming from the database */);
         }
 
-        private boolean add(SyncOperation operation, boolean fromDatabase) {
-            if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(!fromDatabase);
+        private boolean add(SyncOperation operation,
+                SyncStorageEngine.PendingOperation pop) {
+            if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(pop == null);
 
             // If this operation is expedited then set its earliestRunTime to be immediately
             // before the head of the list, or not if none are in the list.
@@ -1996,7 +1992,7 @@
 
             if (existingOperation != null
                     && operation.earliestRunTime >= existingOperation.earliestRunTime) {
-                if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(!fromDatabase);
+                if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(pop == null);
                 return false;
             }
 
@@ -2004,26 +2000,17 @@
                 removeByKey(operationKey);
             }
 
-            if (operation.rowId == null) {
-                byte[] extrasData = null;
-                Parcel parcel = Parcel.obtain();
-                try {
-                    operation.extras.writeToParcel(parcel, 0);
-                    extrasData = parcel.marshall();
-                } finally {
-                    parcel.recycle();
-                }
-                ContentValues values = new ContentValues();
-                values.put("account", operation.account);
-                values.put("authority", operation.authority);
-                values.put("source", operation.syncSource);
-                values.put("extras", extrasData);
-                Uri pendingUri = mSyncStorageEngine.insertIntoPending(values);
-                operation.rowId = pendingUri == null ? null : ContentUris.parseId(pendingUri);
-                if (operation.rowId == null) {
+            operation.pendingOperation = pop;
+            if (operation.pendingOperation == null) {
+                pop = new SyncStorageEngine.PendingOperation(
+                                operation.account, operation.syncSource,
+                                operation.authority, operation.extras);
+                pop = mSyncStorageEngine.insertIntoPending(pop);
+                if (pop == null) {
                     throw new IllegalStateException("error adding pending sync operation "
                             + operation);
                 }
+                operation.pendingOperation = pop;
             }
 
             if (DEBUG_CHECK_DATA_CONSISTENCY) {
@@ -2033,7 +2020,7 @@
             }
             mOpsByKey.put(operationKey, operation);
             mOpsByWhen.add(operation);
-            if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(!fromDatabase);
+            if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(pop == null);
             return true;
         }
 
@@ -2045,7 +2032,7 @@
                         "unable to find " + operationToRemove + " in mOpsByWhen");
             }
 
-            if (mSyncStorageEngine.deleteFromPending(operationToRemove.rowId) != 1) {
+            if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) {
                 throw new IllegalStateException("unable to find pending row for "
                         + operationToRemove);
             }
@@ -2065,7 +2052,7 @@
                 throw new IllegalStateException("unable to find " + operation + " in mOpsByKey");
             }
 
-            if (mSyncStorageEngine.deleteFromPending(operation.rowId) != 1) {
+            if (!mSyncStorageEngine.deleteFromPending(operation.pendingOperation)) {
                 throw new IllegalStateException("unable to find pending row for " + operation);
             }
 
@@ -2087,7 +2074,7 @@
                             "unable to find " + syncOperation + " in mOpsByWhen");
                 }
 
-                if (mSyncStorageEngine.deleteFromPending(syncOperation.rowId) != 1) {
+                if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) {
                     throw new IllegalStateException("unable to find pending row for "
                             + syncOperation);
                 }
@@ -2128,48 +2115,29 @@
             }
 
             if (checkDatabase) {
-                // check that the DB contains the same rows as the in-memory data structures
-                Cursor cursor = mSyncStorageEngine.getPendingSyncsCursor(COLUMNS);
-                try {
-                    if (mOpsByKey.size() != cursor.getCount()) {
-                        StringBuilder sb = new StringBuilder();
-                        DatabaseUtils.dumpCursor(cursor, sb);
+                final int N = mSyncStorageEngine.getPendingOperationCount();
+                if (mOpsByKey.size() != N) {
+                    ArrayList<SyncStorageEngine.PendingOperation> ops
+                            = mSyncStorageEngine.getPendingOperations();
+                    StringBuilder sb = new StringBuilder();
+                    for (int i=0; i<N; i++) {
+                        SyncStorageEngine.PendingOperation op = ops.get(i);
+                        sb.append("#");
+                        sb.append(i);
+                        sb.append(": account=");
+                        sb.append(op.account);
+                        sb.append(" syncSource=");
+                        sb.append(op.syncSource);
+                        sb.append(" authority=");
+                        sb.append(op.authority);
                         sb.append("\n");
-                        dump(sb);
-                        throw new IllegalStateException("DB size mismatch: "
-                                + mOpsByKey .size() + " != " + cursor.getCount() + "\n"
-                                + sb.toString());
                     }
-                } finally {
-                    cursor.close();
+                    dump(sb);
+                    throw new IllegalStateException("DB size mismatch: "
+                            + mOpsByKey.size() + " != " + N + "\n"
+                            + sb.toString());
                 }
             }
         }
-
-        private SyncOperation cursorToOperation(Cursor cursor) {
-            byte[] extrasData = cursor.getBlob(COLUMN_EXTRAS);
-            Bundle extras;
-            Parcel parcel = Parcel.obtain();
-            try {
-                parcel.unmarshall(extrasData, 0, extrasData.length);
-                parcel.setDataPosition(0);
-                extras = parcel.readBundle();
-            } catch (RuntimeException e) {
-                // A RuntimeException is thrown if we were unable to parse the parcel.
-                // Create an empty parcel in this case.
-                extras = new Bundle();
-            } finally {
-                parcel.recycle();
-            }
-
-            SyncOperation syncOperation = new SyncOperation(
-                    cursor.getString(COLUMN_ACCOUNT),
-                    cursor.getInt(COLUMN_SOURCE),
-                    cursor.getString(COLUMN_AUTHORITY),
-                    extras,
-                    0 /* delay */);
-            syncOperation.rowId = cursor.getLong(COLUMN_ID);
-            return syncOperation;
-        }
     }
 }
diff --git a/core/java/android/content/SyncProvider.java b/core/java/android/content/SyncProvider.java
deleted file mode 100644
index 6ddd046..0000000
--- a/core/java/android/content/SyncProvider.java
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2007 The Android Open Source Project
-package android.content;
-
-import android.database.Cursor;
-import android.net.Uri;
-
-/**
- * ContentProvider that tracks the sync data and overall sync
- * history on the device.
- * 
- * @hide
- */
-public class SyncProvider extends ContentProvider {
-    public SyncProvider() {
-    }
-
-    private SyncStorageEngine mSyncStorageEngine;
-
-    @Override
-    public boolean onCreate() {
-        mSyncStorageEngine = SyncStorageEngine.getSingleton();
-        return true;
-    }
-
-    @Override
-    public Cursor query(Uri url, String[] projectionIn,
-                        String selection, String[] selectionArgs, String sort) {
-        return mSyncStorageEngine.query(url, projectionIn, selection, selectionArgs, sort);
-    }
-
-    @Override
-    public Uri insert(Uri url, ContentValues initialValues) {
-        return mSyncStorageEngine.insert(true /* the caller is the provider */,
-                url, initialValues);
-    }
-
-    @Override
-    public int delete(Uri url, String where, String[] whereArgs) {
-        return mSyncStorageEngine.delete(true /* the caller is the provider */,
-                url, where, whereArgs);
-    }
-
-    @Override
-    public int update(Uri url, ContentValues initialValues, String where, String[] whereArgs) {
-        return mSyncStorageEngine.update(true /* the caller is the provider */, 
-                url, initialValues, where, whereArgs);
-    }
-
-    @Override
-    public String getType(Uri url) {
-        return mSyncStorageEngine.getType(url);
-    }
-}
diff --git a/core/java/android/content/SyncStatusInfo.aidl b/core/java/android/content/SyncStatusInfo.aidl
new file mode 100644
index 0000000..b188c0b
--- /dev/null
+++ b/core/java/android/content/SyncStatusInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+parcelable SyncStatusInfo;
diff --git a/core/java/android/content/SyncStatusInfo.java b/core/java/android/content/SyncStatusInfo.java
new file mode 100644
index 0000000..6687fcb
--- /dev/null
+++ b/core/java/android/content/SyncStatusInfo.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/** @hide */
+public class SyncStatusInfo implements Parcelable {
+    static final int VERSION = 1;
+    
+    public final int authorityId;
+    public long totalElapsedTime;
+    public int numSyncs;
+    public int numSourcePoll;
+    public int numSourceServer;
+    public int numSourceLocal;
+    public int numSourceUser;
+    public long lastSuccessTime;
+    public int lastSuccessSource;
+    public long lastFailureTime;
+    public int lastFailureSource;
+    public String lastFailureMesg;
+    public long initialFailureTime;
+    public boolean pending;
+    
+    SyncStatusInfo(int authorityId) {
+        this.authorityId = authorityId;
+    }
+
+    public int getLastFailureMesgAsInt(int def) {
+        try {
+            if (lastFailureMesg != null) {
+                return Integer.parseInt(lastFailureMesg);
+            }
+        } catch (NumberFormatException e) {
+        }
+        return def;
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(VERSION);
+        parcel.writeInt(authorityId);
+        parcel.writeLong(totalElapsedTime);
+        parcel.writeInt(numSyncs);
+        parcel.writeInt(numSourcePoll);
+        parcel.writeInt(numSourceServer);
+        parcel.writeInt(numSourceLocal);
+        parcel.writeInt(numSourceUser);
+        parcel.writeLong(lastSuccessTime);
+        parcel.writeInt(lastSuccessSource);
+        parcel.writeLong(lastFailureTime);
+        parcel.writeInt(lastFailureSource);
+        parcel.writeString(lastFailureMesg);
+        parcel.writeLong(initialFailureTime);
+        parcel.writeInt(pending ? 1 : 0);
+    }
+
+    SyncStatusInfo(Parcel parcel) {
+        int version = parcel.readInt();
+        if (version != VERSION) {
+            Log.w("SyncStatusInfo", "Unknown version: " + version);
+        }
+        authorityId = parcel.readInt();
+        totalElapsedTime = parcel.readLong();
+        numSyncs = parcel.readInt();
+        numSourcePoll = parcel.readInt();
+        numSourceServer = parcel.readInt();
+        numSourceLocal = parcel.readInt();
+        numSourceUser = parcel.readInt();
+        lastSuccessTime = parcel.readLong();
+        lastSuccessSource = parcel.readInt();
+        lastFailureTime = parcel.readLong();
+        lastFailureSource = parcel.readInt();
+        lastFailureMesg = parcel.readString();
+        initialFailureTime = parcel.readLong();
+        pending = parcel.readInt() != 0;
+    }
+    
+    public static final Creator<SyncStatusInfo> CREATOR = new Creator<SyncStatusInfo>() {
+        public SyncStatusInfo createFromParcel(Parcel in) {
+            return new SyncStatusInfo(in);
+        }
+
+        public SyncStatusInfo[] newArray(int size) {
+            return new SyncStatusInfo[size];
+        }
+    };
+}
\ No newline at end of file
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index 282f6e7..1587462 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -1,125 +1,282 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package android.content;
 
-import android.Manifest;
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.net.Uri;
-import android.provider.Sync;
-import android.text.TextUtils;
-import android.util.Config;
-import android.util.Log;
+import com.android.internal.os.AtomicFile;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastXmlSerializer;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.HashMap;
-import java.util.HashSet;
+import java.util.Iterator;
+import java.util.TimeZone;
 
 /**
- * ContentProvider that tracks the sync data and overall sync
+ * Singleton that tracks the sync data and overall sync
  * history on the device.
  * 
  * @hide
  */
-public class SyncStorageEngine {
+public class SyncStorageEngine extends Handler {
     private static final String TAG = "SyncManager";
+    private static final boolean DEBUG = false;
+    private static final boolean DEBUG_FILE = false;
+    
+    // @VisibleForTesting
+    static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4;
 
-    private static final String DATABASE_NAME = "syncmanager.db";
-    private static final int DATABASE_VERSION = 10;
+    /** Enum value for a sync start event. */
+    public static final int EVENT_START = 0;
 
-    private static final int STATS = 1;
-    private static final int STATS_ID = 2;
-    private static final int HISTORY = 3;
-    private static final int HISTORY_ID = 4;
-    private static final int SETTINGS = 5;
-    private static final int PENDING = 7;
-    private static final int ACTIVE = 8;
-    private static final int STATUS = 9;
+    /** Enum value for a sync stop event. */
+    public static final int EVENT_STOP = 1;
 
-    private static final UriMatcher sURLMatcher =
-            new UriMatcher(UriMatcher.NO_MATCH);
+    // TODO: i18n -- grab these out of resources.
+    /** String names for the sync event types. */
+    public static final String[] EVENTS = { "START", "STOP" };
 
-    private static final HashMap<String,String> HISTORY_PROJECTION_MAP;
-    private static final HashMap<String,String> PENDING_PROJECTION_MAP;
-    private static final HashMap<String,String> ACTIVE_PROJECTION_MAP;
-    private static final HashMap<String,String> STATUS_PROJECTION_MAP;
+    /** Enum value for a server-initiated sync. */
+    public static final int SOURCE_SERVER = 0;
 
-    private final Context mContext;
-    private final SQLiteOpenHelper mOpenHelper;
-    private static SyncStorageEngine sSyncStorageEngine = null;
+    /** 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)
+     */
+    public static final int SOURCE_POLL = 2;
 
-    static {
-        sURLMatcher.addURI("sync", "stats", STATS);
-        sURLMatcher.addURI("sync", "stats/#", STATS_ID);
-        sURLMatcher.addURI("sync", "history", HISTORY);
-        sURLMatcher.addURI("sync", "history/#", HISTORY_ID);
-        sURLMatcher.addURI("sync", "settings", SETTINGS);
-        sURLMatcher.addURI("sync", "status", STATUS);
-        sURLMatcher.addURI("sync", "active", ACTIVE);
-        sURLMatcher.addURI("sync", "pending", PENDING);
+    /** Enum value for a user-initiated sync. */
+    public static final int SOURCE_USER = 3;
 
-        HashMap<String,String> map;
-        PENDING_PROJECTION_MAP = map = new HashMap<String,String>();
-        map.put(Sync.History._ID, Sync.History._ID);
-        map.put(Sync.History.ACCOUNT, Sync.History.ACCOUNT);
-        map.put(Sync.History.AUTHORITY, Sync.History.AUTHORITY);
+    // TODO: i18n -- grab these out of resources.
+    /** String names for the sync source types. */
+    public static final String[] SOURCES = { "SERVER",
+                                             "LOCAL",
+                                             "POLL",
+                                             "USER" };
 
-        ACTIVE_PROJECTION_MAP = map = new HashMap<String,String>();
-        map.put(Sync.History._ID, Sync.History._ID);
-        map.put(Sync.History.ACCOUNT, Sync.History.ACCOUNT);
-        map.put(Sync.History.AUTHORITY, Sync.History.AUTHORITY);
-        map.put("startTime", "startTime");
+    // Error types
+    public static final int ERROR_SYNC_ALREADY_IN_PROGRESS = 1;
+    public static final int ERROR_AUTHENTICATION = 2;
+    public static final int ERROR_IO = 3;
+    public static final int ERROR_PARSE = 4;
+    public static final int ERROR_CONFLICT = 5;
+    public static final int ERROR_TOO_MANY_DELETIONS = 6;
+    public static final int ERROR_TOO_MANY_RETRIES = 7;
+    public static final int ERROR_INTERNAL = 8;
 
-        HISTORY_PROJECTION_MAP = map = new HashMap<String,String>();
-        map.put(Sync.History._ID, "history._id as _id");
-        map.put(Sync.History.ACCOUNT, "stats.account as account");
-        map.put(Sync.History.AUTHORITY, "stats.authority as authority");
-        map.put(Sync.History.EVENT, Sync.History.EVENT);
-        map.put(Sync.History.EVENT_TIME, Sync.History.EVENT_TIME);
-        map.put(Sync.History.ELAPSED_TIME, Sync.History.ELAPSED_TIME);
-        map.put(Sync.History.SOURCE, Sync.History.SOURCE);
-        map.put(Sync.History.UPSTREAM_ACTIVITY, Sync.History.UPSTREAM_ACTIVITY);
-        map.put(Sync.History.DOWNSTREAM_ACTIVITY, Sync.History.DOWNSTREAM_ACTIVITY);
-        map.put(Sync.History.MESG, Sync.History.MESG);
+    // The MESG column will contain one of these or one of the Error types.
+    public static final String MESG_SUCCESS = "success";
+    public static final String MESG_CANCELED = "canceled";
 
-        STATUS_PROJECTION_MAP = map = new HashMap<String,String>();
-        map.put(Sync.Status._ID, "status._id as _id");
-        map.put(Sync.Status.ACCOUNT, "stats.account as account");
-        map.put(Sync.Status.AUTHORITY, "stats.authority as authority");
-        map.put(Sync.Status.TOTAL_ELAPSED_TIME, Sync.Status.TOTAL_ELAPSED_TIME);
-        map.put(Sync.Status.NUM_SYNCS, Sync.Status.NUM_SYNCS);
-        map.put(Sync.Status.NUM_SOURCE_LOCAL, Sync.Status.NUM_SOURCE_LOCAL);
-        map.put(Sync.Status.NUM_SOURCE_POLL, Sync.Status.NUM_SOURCE_POLL);
-        map.put(Sync.Status.NUM_SOURCE_SERVER, Sync.Status.NUM_SOURCE_SERVER);
-        map.put(Sync.Status.NUM_SOURCE_USER, Sync.Status.NUM_SOURCE_USER);
-        map.put(Sync.Status.LAST_SUCCESS_SOURCE, Sync.Status.LAST_SUCCESS_SOURCE);
-        map.put(Sync.Status.LAST_SUCCESS_TIME, Sync.Status.LAST_SUCCESS_TIME);
-        map.put(Sync.Status.LAST_FAILURE_SOURCE, Sync.Status.LAST_FAILURE_SOURCE);
-        map.put(Sync.Status.LAST_FAILURE_TIME, Sync.Status.LAST_FAILURE_TIME);
-        map.put(Sync.Status.LAST_FAILURE_MESG, Sync.Status.LAST_FAILURE_MESG);
-        map.put(Sync.Status.PENDING, Sync.Status.PENDING);
+    public static final int CHANGE_SETTINGS = 1<<0;
+    public static final int CHANGE_PENDING = 1<<1;
+    public static final int CHANGE_ACTIVE = 1<<2;
+    public static final int CHANGE_STATUS = 1<<3;
+    public static final int CHANGE_ALL = 0x7fffffff;
+    
+    public static final int MAX_HISTORY = 15;
+    
+    private static final int MSG_WRITE_STATUS = 1;
+    private static final long WRITE_STATUS_DELAY = 1000*60*10; // 10 minutes
+    
+    private static final int MSG_WRITE_STATISTICS = 2;
+    private static final long WRITE_STATISTICS_DELAY = 1000*60*30; // 1/2 hour
+    
+    public static class PendingOperation {
+        final String account;
+        final int syncSource;
+        final String authority;
+        final Bundle extras;        // note: read-only.
+        
+        int authorityId;
+        byte[] flatExtras;
+        
+        PendingOperation(String account, int source,
+                String authority, Bundle extras) {
+            this.account = account;
+            this.syncSource = source;
+            this.authority = authority;
+            this.extras = extras != null ? new Bundle(extras) : extras;
+            this.authorityId = -1;
+        }
+
+        PendingOperation(PendingOperation other) {
+            this.account = other.account;
+            this.syncSource = other.syncSource;
+            this.authority = other.authority;
+            this.extras = other.extras;
+            this.authorityId = other.authorityId;
+        }
     }
+    
+    static class AccountInfo {
+        final String account;
+        final HashMap<String, AuthorityInfo> authorities =
+                new HashMap<String, AuthorityInfo>();
+        
+        AccountInfo(String account) {
+            this.account = account;
+        }
+    }
+    
+    public static class AuthorityInfo {
+        final String account;
+        final String authority;
+        final int ident;
+        boolean enabled;
+        
+        AuthorityInfo(String account, String authority, int ident) {
+            this.account = account;
+            this.authority = authority;
+            this.ident = ident;
+            enabled = true;
+        }
+    }
+    
+    public static class SyncHistoryItem {
+        int authorityId;
+        int historyId;
+        long eventTime;
+        long elapsedTime;
+        int source;
+        int event;
+        long upstreamActivity;
+        long downstreamActivity;
+        String mesg;
+    }
+    
+    public static class DayStats {
+        public final int day;
+        public int successCount;
+        public long successTime;
+        public int failureCount;
+        public long failureTime;
+        
+        public DayStats(int day) {
+            this.day = day;
+        }
+    }
+    
+    // Primary list of all syncable authorities.  Also our global lock.
+    private final SparseArray<AuthorityInfo> mAuthorities =
+            new SparseArray<AuthorityInfo>();
+    
+    private final HashMap<String, AccountInfo> mAccounts =
+        new HashMap<String, AccountInfo>();
 
-    private static final String[] STATS_ACCOUNT_PROJECTION =
-            new String[] { Sync.Stats.ACCOUNT };
-
-    private static final int MAX_HISTORY_EVENTS_TO_KEEP = 5000;
-
-    private static final String SELECT_INITIAL_FAILURE_TIME_QUERY_STRING = ""
-            + "SELECT min(a) "
-            + "FROM ("
-            + "  SELECT initialFailureTime AS a "
-            + "  FROM status "
-            + "  WHERE stats_id=? AND a IS NOT NULL "
-            + "    UNION "
-            + "  SELECT ? AS a"
-            + " )";
-
+    private final ArrayList<PendingOperation> mPendingOperations =
+            new ArrayList<PendingOperation>();
+    
+    private ActiveSyncInfo mActiveSync;
+    
+    private final SparseArray<SyncStatusInfo> mSyncStatus =
+            new SparseArray<SyncStatusInfo>();
+    
+    private final ArrayList<SyncHistoryItem> mSyncHistory =
+            new ArrayList<SyncHistoryItem>();
+    
+    private final RemoteCallbackList<ISyncStatusObserver> mChangeListeners
+            = new RemoteCallbackList<ISyncStatusObserver>();
+    
+    // We keep 4 weeks of stats.
+    private final DayStats[] mDayStats = new DayStats[7*4];
+    private final Calendar mCal;
+    private int mYear;
+    private int mYearInDays;
+    
+    private final Context mContext;
+    private static volatile SyncStorageEngine sSyncStorageEngine = null;
+    
+    /**
+     * This file contains the core engine state: all accounts and the
+     * settings for them.  It must never be lost, and should be changed
+     * infrequently, so it is stored as an XML file.
+     */
+    private final AtomicFile mAccountInfoFile;
+    
+    /**
+     * This file contains the current sync status.  We would like to retain
+     * it across boots, but its loss is not the end of the world, so we store
+     * this information as binary data.
+     */
+    private final AtomicFile mStatusFile;
+    
+    /**
+     * This file contains sync statistics.  This is purely debugging information
+     * so is written infrequently and can be thrown away at any time.
+     */
+    private final AtomicFile mStatisticsFile;
+    
+    /**
+     * This file contains the pending sync operations.  It is a binary file,
+     * which must be updated every time an operation is added or removed,
+     * so we have special handling of it.
+     */
+    private final AtomicFile mPendingFile;
+    private static final int PENDING_FINISH_TO_WRITE = 4;
+    private int mNumPendingFinished = 0;
+    
+    private int mNextHistoryId = 0;
+    private boolean mListenForTickles = true;
+    
     private SyncStorageEngine(Context context) {
         mContext = context;
-        mOpenHelper = new SyncStorageEngine.DatabaseHelper(context);
         sSyncStorageEngine = this;
+        
+        mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
+        
+        File dataDir = Environment.getDataDirectory();
+        File systemDir = new File(dataDir, "system");
+        File syncDir = new File(systemDir, "sync");
+        mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
+        mStatusFile = new AtomicFile(new File(syncDir, "status.bin"));
+        mPendingFile = new AtomicFile(new File(syncDir, "pending.bin"));
+        mStatisticsFile = new AtomicFile(new File(syncDir, "stats.bin"));
+        
+        readAccountInfoLocked();
+        readStatusLocked();
+        readPendingOperationsLocked();
+        readStatisticsLocked();
+        readLegacyAccountInfoLocked();
     }
 
     public static SyncStorageEngine newTestInstance(Context context) {
@@ -140,619 +297,1249 @@
         return sSyncStorageEngine;
     }
 
-    private class DatabaseHelper extends SQLiteOpenHelper {
-        DatabaseHelper(Context context) {
-            super(context, DATABASE_NAME, null, DATABASE_VERSION);
-        }
-
-        @Override
-        public void onCreate(SQLiteDatabase db) {
-            db.execSQL("CREATE TABLE pending ("
-                    + "_id INTEGER PRIMARY KEY,"
-                    + "authority TEXT NOT NULL,"
-                    + "account TEXT NOT NULL,"
-                    + "extras BLOB NOT NULL,"
-                    + "source INTEGER NOT NULL"
-                    + ");");
-
-            db.execSQL("CREATE TABLE stats (" +
-                       "_id INTEGER PRIMARY KEY," +
-                       "account TEXT, " +
-                       "authority TEXT, " +
-                       "syncdata TEXT, " +
-                       "UNIQUE (account, authority)" +
-                       ");");
-
-            db.execSQL("CREATE TABLE history (" +
-                       "_id INTEGER PRIMARY KEY," +
-                       "stats_id INTEGER," +
-                       "eventTime INTEGER," +
-                       "elapsedTime INTEGER," +
-                       "source INTEGER," +
-                       "event INTEGER," +
-                       "upstreamActivity INTEGER," +
-                       "downstreamActivity INTEGER," +
-                       "mesg TEXT);");
-
-            db.execSQL("CREATE TABLE status ("
-                    + "_id INTEGER PRIMARY KEY,"
-                    + "stats_id INTEGER NOT NULL,"
-                    + "totalElapsedTime INTEGER NOT NULL DEFAULT 0,"
-                    + "numSyncs INTEGER NOT NULL DEFAULT 0,"
-                    + "numSourcePoll INTEGER NOT NULL DEFAULT 0,"
-                    + "numSourceServer INTEGER NOT NULL DEFAULT 0,"
-                    + "numSourceLocal INTEGER NOT NULL DEFAULT 0,"
-                    + "numSourceUser INTEGER NOT NULL DEFAULT 0,"
-                    + "lastSuccessTime INTEGER,"
-                    + "lastSuccessSource INTEGER,"
-                    + "lastFailureTime INTEGER,"
-                    + "lastFailureSource INTEGER,"
-                    + "lastFailureMesg STRING,"
-                    + "initialFailureTime INTEGER,"
-                    + "pending INTEGER NOT NULL DEFAULT 0);");
-
-            db.execSQL("CREATE TABLE active ("
-                    + "_id INTEGER PRIMARY KEY,"
-                    + "authority TEXT,"
-                    + "account TEXT,"
-                    + "startTime INTEGER);");
-
-            db.execSQL("CREATE INDEX historyEventTime ON history (eventTime)");
-
-            db.execSQL("CREATE TABLE settings (" +
-                       "name TEXT PRIMARY KEY," +
-                       "value TEXT);");
-        }
-
-        @Override
-        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-            if (oldVersion == 9 && newVersion == 10) {
-                Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
-                        + newVersion + ", which will preserve old data");
-                db.execSQL("ALTER TABLE status ADD COLUMN initialFailureTime INTEGER");
-                return;
+    @Override public void handleMessage(Message msg) {
+        if (msg.what == MSG_WRITE_STATUS) {
+            synchronized (mAccounts) {
+                writeStatusLocked();
             }
-
-            Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
-                    + newVersion + ", which will destroy all old data");
-            db.execSQL("DROP TABLE IF EXISTS pending");
-            db.execSQL("DROP TABLE IF EXISTS stats");
-            db.execSQL("DROP TABLE IF EXISTS history");
-            db.execSQL("DROP TABLE IF EXISTS settings");
-            db.execSQL("DROP TABLE IF EXISTS active");
-            db.execSQL("DROP TABLE IF EXISTS status");
-            onCreate(db);
-        }
-
-        @Override
-        public void onOpen(SQLiteDatabase db) {
-            if (!db.isReadOnly()) {
-                db.delete("active", null, null);
-                db.insert("active", "account", null);
+        } else if (msg.what == MSG_WRITE_STATISTICS) {
+            synchronized (mAccounts) {
+                writeStatisticsLocked();
             }
         }
     }
-
-    protected void doDatabaseCleanup(String[] accounts) {
-        HashSet<String> currentAccounts = new HashSet<String>();
-        for (String account : accounts) currentAccounts.add(account);
-        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
-        Cursor cursor = db.query("stats", STATS_ACCOUNT_PROJECTION,
-                null /* where */, null /* where args */, Sync.Stats.ACCOUNT,
-                null /* having */, null /* order by */);
-        try {
-            while (cursor.moveToNext()) {
-                String account = cursor.getString(0);
-                if (TextUtils.isEmpty(account)) {
+    
+    public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
+        synchronized (mAuthorities) {
+            mChangeListeners.register(callback, mask);
+        }
+    }
+    
+    public void removeStatusChangeListener(ISyncStatusObserver callback) {
+        synchronized (mAuthorities) {
+            mChangeListeners.unregister(callback);
+        }
+    }
+    
+    private void reportChange(int which) {
+        ArrayList<ISyncStatusObserver> reports = null;
+        synchronized (mAuthorities) {
+            int i = mChangeListeners.beginBroadcast();
+            while (i > 0) {
+                i--;
+                Integer mask = (Integer)mChangeListeners.getBroadcastCookie(i);
+                if ((which & mask.intValue()) == 0) {
                     continue;
                 }
-                if (!currentAccounts.contains(account)) {
-                    String where = Sync.Stats.ACCOUNT + "=?";
-                    int numDeleted;
-                    numDeleted = db.delete("stats", where, new String[]{account});
-                    if (Config.LOGD) {
-                        Log.d(TAG, "deleted " + numDeleted
-                                + " records from stats table"
-                                + " for account " + account);
+                if (reports == null) {
+                    reports = new ArrayList<ISyncStatusObserver>(i);
+                }
+                reports.add(mChangeListeners.getBroadcastItem(i));
+            }
+        }
+        
+        if (DEBUG) Log.v(TAG, "reportChange " + which + " to: " + reports);
+        
+        if (reports != null) {
+            int i = reports.size();
+            while (i > 0) {
+                i--;
+                try {
+                    reports.get(i).onStatusChanged(which);
+                } catch (RemoteException e) {
+                    // The remote callback list will take care of this for us.
+                }
+            }
+        }
+    }
+    
+    public boolean getSyncProviderAutomatically(String account, String providerName) {
+        synchronized (mAuthorities) {
+            if (account != null) {
+                AuthorityInfo authority = getAuthorityLocked(account, providerName,
+                        "getSyncProviderAutomatically");
+                return authority != null ? authority.enabled : false;
+            }
+            
+            int i = mAuthorities.size();
+            while (i > 0) {
+                i--;
+                AuthorityInfo authority = mAuthorities.get(i);
+                if (authority.authority.equals(providerName)
+                        && authority.enabled) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    public void setSyncProviderAutomatically(String account, String providerName, boolean sync) {
+        synchronized (mAuthorities) {
+            if (account != null) {
+                AuthorityInfo authority = getAuthorityLocked(account, providerName,
+                        "setSyncProviderAutomatically");
+                if (authority != null) {
+                    authority.enabled = sync;
+                }
+            } else {
+                int i = mAuthorities.size();
+                while (i > 0) {
+                    i--;
+                    AuthorityInfo authority = mAuthorities.get(i);
+                    if (authority.account.equals(account)
+                            && authority.authority.equals(providerName)) {
+                        authority.enabled = sync;
                     }
                 }
             }
-        } finally {
-            cursor.close();
+            writeAccountInfoLocked();
         }
-    }
-
-    protected void setActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
-        if (activeSyncContext != null) {
-            updateActiveSync(activeSyncContext.mSyncOperation.account,
-                    activeSyncContext.mSyncOperation.authority, activeSyncContext.mStartTime);
-        } else {
-            // we indicate that the sync is not active by passing null for all the parameters
-            updateActiveSync(null, null, null);
-        }
-    }
-
-    private int updateActiveSync(String account, String authority, Long startTime) {
-        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
-        ContentValues values = new ContentValues();
-        values.put("account", account);
-        values.put("authority", authority);
-        values.put("startTime", startTime);
-        int numChanges = db.update("active", values, null, null);
-        if (numChanges > 0) {
-            mContext.getContentResolver().notifyChange(Sync.Active.CONTENT_URI,
-                    null /* this change wasn't made through an observer */);
-        }
-        return numChanges;
-    }
-
-    /**
-     * Implements the {@link ContentProvider#query} method
-     */
-    public Cursor query(Uri url, String[] projectionIn,
-            String selection, String[] selectionArgs, String sort) {
-        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
-
-        // Generate the body of the query
-        int match = sURLMatcher.match(url);
-        String groupBy = null;
-        switch (match) {
-            case STATS:
-                qb.setTables("stats");
-                break;
-            case STATS_ID:
-                qb.setTables("stats");
-                qb.appendWhere("_id=");
-                qb.appendWhere(url.getPathSegments().get(1));
-                break;
-            case HISTORY:
-                // join the stats and history tables, so the caller can get
-                // the account and authority information as part of this query.
-                qb.setTables("stats, history");
-                qb.setProjectionMap(HISTORY_PROJECTION_MAP);
-                qb.appendWhere("stats._id = history.stats_id");
-                break;
-            case ACTIVE:
-                qb.setTables("active");
-                qb.setProjectionMap(ACTIVE_PROJECTION_MAP);
-                qb.appendWhere("account is not null");
-                break;
-            case PENDING:
-                qb.setTables("pending");
-                qb.setProjectionMap(PENDING_PROJECTION_MAP);
-                groupBy = "account, authority";
-                break;
-            case STATUS:
-                // join the stats and status tables, so the caller can get
-                // the account and authority information as part of this query.
-                qb.setTables("stats, status");
-                qb.setProjectionMap(STATUS_PROJECTION_MAP);
-                qb.appendWhere("stats._id = status.stats_id");
-                break;
-            case HISTORY_ID:
-                // join the stats and history tables, so the caller can get
-                // the account and authority information as part of this query.
-                qb.setTables("stats, history");
-                qb.setProjectionMap(HISTORY_PROJECTION_MAP);
-                qb.appendWhere("stats._id = history.stats_id");
-                qb.appendWhere("AND history._id=");
-                qb.appendWhere(url.getPathSegments().get(1));
-                break;
-            case SETTINGS:
-                qb.setTables("settings");
-                break;
-            default:
-                throw new IllegalArgumentException("Unknown URL " + url);
-        }
-
-        if (match == SETTINGS) {
-            mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
-                    "no permission to read the sync settings");
-        } else {
-            mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
-                    "no permission to read the sync stats");
-        }
-        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
-        Cursor c = qb.query(db, projectionIn, selection, selectionArgs, groupBy, null, sort);
-        c.setNotificationUri(mContext.getContentResolver(), url);
-        return c;
-    }
-
-    /**
-     * Implements the {@link ContentProvider#insert} method
-     * @param callerIsTheProvider true if this is being called via the
-     *  {@link ContentProvider#insert} in method rather than directly.
-     * @throws UnsupportedOperationException if callerIsTheProvider is true and the url isn't
-     *   for the Settings table.
-     */
-    public Uri insert(boolean callerIsTheProvider, Uri url, ContentValues values) {
-        String table;
-        long rowID;
-        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
-        final int match = sURLMatcher.match(url);
-        checkCaller(callerIsTheProvider, match);
-        switch (match) {
-            case SETTINGS:
-                mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
-                        "no permission to write the sync settings");
-                table = "settings";
-                rowID = db.replace(table, null, values);
-                break;
-            default:
-                throw new IllegalArgumentException("Unknown URL " + url);
-        }
-
-
-        if (rowID > 0) {
-            mContext.getContentResolver().notifyChange(url, null /* observer */);
-            return Uri.parse("content://sync/" + table + "/" + rowID);
-        }
-
-        return null;
-    }
-
-    private static void checkCaller(boolean callerIsTheProvider, int match) {
-        if (callerIsTheProvider && match != SETTINGS) {
-            throw new UnsupportedOperationException(
-                    "only the settings are modifiable via the ContentProvider interface");
-        }
-    }
-
-    /**
-     * Implements the {@link ContentProvider#delete} method
-     * @param callerIsTheProvider true if this is being called via the
-     *  {@link ContentProvider#delete} in method rather than directly.
-     * @throws UnsupportedOperationException if callerIsTheProvider is true and the url isn't
-     *   for the Settings table.
-     */
-    public int delete(boolean callerIsTheProvider, Uri url, String where, String[] whereArgs) {
-        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
-        int match = sURLMatcher.match(url);
-
-        int numRows;
-        switch (match) {
-            case SETTINGS:
-                mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
-                        "no permission to write the sync settings");
-                numRows = db.delete("settings", where, whereArgs);
-                break;
-            default:
-                throw new UnsupportedOperationException("Cannot delete URL: " + url);
-        }
-
-        if (numRows > 0) {
-            mContext.getContentResolver().notifyChange(url, null /* observer */);
-        }
-        return numRows;
-    }
-
-    /**
-     * Implements the {@link ContentProvider#update} method
-     * @param callerIsTheProvider true if this is being called via the
-     *  {@link ContentProvider#update} in method rather than directly.
-     * @throws UnsupportedOperationException if callerIsTheProvider is true and the url isn't
-     *   for the Settings table.
-     */
-    public int update(boolean callerIsTheProvider, Uri url, ContentValues initialValues,
-            String where, String[] whereArgs) {
-        switch (sURLMatcher.match(url)) {
-            case SETTINGS:
-                throw new UnsupportedOperationException("updating url " + url
-                        + " is not allowed, use insert instead");
-            default:
-                throw new UnsupportedOperationException("Cannot update URL: " + url);
-        }
-    }
-
-    /**
-     * Implements the {@link ContentProvider#getType} method
-     */
-    public String getType(Uri url) {
-        int match = sURLMatcher.match(url);
-        switch (match) {
-            case SETTINGS:
-                return "vnd.android.cursor.dir/sync-settings";
-            default:
-                throw new IllegalArgumentException("Unknown URL");
-        }
-    }
-
-    protected Uri insertIntoPending(ContentValues values) {
-        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
-        try {
-            db.beginTransaction();
-            long rowId = db.insert("pending", Sync.Pending.ACCOUNT, values);
-            if (rowId < 0) return null;
-            String account = values.getAsString(Sync.Pending.ACCOUNT);
-            String authority = values.getAsString(Sync.Pending.AUTHORITY);
-
-            long statsId = createStatsRowIfNecessary(account, authority);
-            createStatusRowIfNecessary(statsId);
-
-            values.clear();
-            values.put(Sync.Status.PENDING, 1);
-            int numUpdatesStatus = db.update("status", values, "stats_id=" + statsId, null);
-
-            db.setTransactionSuccessful();
-
-            mContext.getContentResolver().notifyChange(Sync.Pending.CONTENT_URI,
-                    null /* no observer initiated this change */);
-            if (numUpdatesStatus > 0) {
-                mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI,
-                        null /* no observer initiated this change */);
-            }
-            return ContentUris.withAppendedId(Sync.Pending.CONTENT_URI, rowId);
-        } finally {
-            db.endTransaction();
-        }
-    }
-
-    int deleteFromPending(long rowId) {
-        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
-        db.beginTransaction();
-        try {
-            String account;
-            String authority;
-            Cursor c = db.query("pending",
-                    new String[]{Sync.Pending.ACCOUNT, Sync.Pending.AUTHORITY},
-                    "_id=" + rowId, null, null, null, null);
-            try {
-                if (c.getCount() != 1) {
-                    return 0;
-                }
-                c.moveToNext();
-                account = c.getString(0);
-                authority = c.getString(1);
-            } finally {
-                c.close();
-            }
-            db.delete("pending", "_id=" + rowId, null /* no where args */);
-            final String[] accountAuthorityWhereArgs = new String[]{account, authority};
-            boolean isPending = 0 < DatabaseUtils.longForQuery(db,
-                    "SELECT COUNT(*) FROM PENDING WHERE account=? AND authority=?",
-                    accountAuthorityWhereArgs);
-            if (!isPending) {
-                long statsId = createStatsRowIfNecessary(account, authority);
-                db.execSQL("UPDATE status SET pending=0 WHERE stats_id=" + statsId);
-            }
-            db.setTransactionSuccessful();
-
-            mContext.getContentResolver().notifyChange(Sync.Pending.CONTENT_URI,
-                    null /* no observer initiated this change */);
-            if (!isPending) {
-                mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI,
-                        null /* no observer initiated this change */);
-            }
-            return 1;
-        } finally {
-            db.endTransaction();
-        }
-    }
-
-    int clearPending() {
-        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
-        db.beginTransaction();
-        try {
-            int numChanges = db.delete("pending", null, null /* no where args */);
-            if (numChanges > 0) {
-                db.execSQL("UPDATE status SET pending=0");
-                mContext.getContentResolver().notifyChange(Sync.Pending.CONTENT_URI,
-                        null /* no observer initiated this change */);
-                mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI,
-                        null /* no observer initiated this change */);
-            }
-            db.setTransactionSuccessful();
-            return numChanges;
-        } finally {
-            db.endTransaction();
-        }
-    }
-
-    /**
-     * Returns a cursor over all the pending syncs in no particular order. This cursor is not
-     * "live", in that if changes are made to the pending table any observers on this cursor
-     * will not be notified.
-     * @param projection Return only these columns. If null then all columns are returned.
-     * @return the cursor of pending syncs
-     */
-    public Cursor getPendingSyncsCursor(String[] projection) {
-        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
-        return db.query("pending", projection, null, null, null, null, null);
-    }
-
-    // @VisibleForTesting
-    static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4;
-
-    private boolean purgeOldHistoryEvents(long now) {
-        // remove events that are older than MILLIS_IN_4WEEKS
-        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
-        int numDeletes = db.delete("history", "eventTime<" + (now - MILLIS_IN_4WEEKS), null);
-        if (Log.isLoggable(TAG, Log.VERBOSE)) {
-            if (numDeletes > 0) {
-                Log.v(TAG, "deleted " + numDeletes + " old event(s) from the sync history");
-            }
-        }
-
-        // keep only the last MAX_HISTORY_EVENTS_TO_KEEP history events
-        numDeletes += db.delete("history", "eventTime < (select min(eventTime) from "
-                + "(select eventTime from history order by eventTime desc limit ?))",
-                new String[]{String.valueOf(MAX_HISTORY_EVENTS_TO_KEEP)});
         
-        return numDeletes > 0;
+        reportChange(CHANGE_SETTINGS);
     }
 
-    public long insertStartSyncEvent(String account, String authority, long now, int source) {
-        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
-        long statsId = createStatsRowIfNecessary(account, authority);
+    public void setListenForNetworkTickles(boolean flag) {
+        synchronized (mAuthorities) {
+            mListenForTickles = flag;
+            writeAccountInfoLocked();
+        }
+        reportChange(CHANGE_SETTINGS);
+    }
 
-        purgeOldHistoryEvents(now);
-        ContentValues values = new ContentValues();
-        values.put(Sync.History.STATS_ID, statsId);
-        values.put(Sync.History.EVENT_TIME, now);
-        values.put(Sync.History.SOURCE, source);
-        values.put(Sync.History.EVENT, Sync.History.EVENT_START);
-        long rowId = db.insert("history", null, values);
-        mContext.getContentResolver().notifyChange(Sync.History.CONTENT_URI, null /* observer */);
-        mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI, null /* observer */);
-        return rowId;
+    public boolean getListenForNetworkTickles() {
+        synchronized (mAuthorities) {
+            return mListenForTickles;
+        }
+    }
+    
+    public AuthorityInfo getAuthority(String account, String authority) {
+        synchronized (mAuthorities) {
+            return getAuthorityLocked(account, authority, null);
+        }
+    }
+    
+    public AuthorityInfo getAuthority(int authorityId) {
+        synchronized (mAuthorities) {
+            return mAuthorities.get(authorityId);
+        }
+    }
+    
+    /**
+     * Returns true if there is currently a sync operation for the given
+     * account or authority in the pending list, or actively being processed.
+     */
+    public boolean isSyncActive(String account, String authority) {
+        synchronized (mAuthorities) {
+            int i = mPendingOperations.size();
+            while (i > 0) {
+                i--;
+                // TODO(fredq): this probably shouldn't be considering
+                // pending operations.
+                PendingOperation op = mPendingOperations.get(i);
+                if (op.account.equals(account) && op.authority.equals(authority)) {
+                    return true;
+                }
+            }
+            
+            if (mActiveSync != null) {
+                AuthorityInfo ainfo = getAuthority(mActiveSync.authorityId);
+                if (ainfo != null && ainfo.account.equals(account)
+                        && ainfo.authority.equals(authority)) {
+                    return true;
+                }
+            }
+        }
+        
+        return false;
+    }
+    
+    public PendingOperation insertIntoPending(PendingOperation op) {
+        synchronized (mAuthorities) {
+            if (DEBUG) Log.v(TAG, "insertIntoPending: account=" + op.account
+                    + " auth=" + op.authority
+                    + " src=" + op.syncSource
+                    + " extras=" + op.extras);
+            
+            AuthorityInfo authority = getOrCreateAuthorityLocked(op.account,
+                    op.authority,
+                    -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);
+            
+            SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
+            status.pending = true;
+        }
+        
+        reportChange(CHANGE_PENDING);
+        return op;
+    }
+
+    public boolean deleteFromPending(PendingOperation op) {
+        boolean res = false;
+        synchronized (mAuthorities) {
+            if (DEBUG) Log.v(TAG, "deleteFromPending: account=" + op.account
+                    + " auth=" + op.authority
+                    + " src=" + op.syncSource
+                    + " extras=" + op.extras);
+            if (mPendingOperations.remove(op)) {
+                if (mPendingOperations.size() == 0
+                        || mNumPendingFinished >= PENDING_FINISH_TO_WRITE) {
+                    writePendingOperationsLocked();
+                    mNumPendingFinished = 0;
+                } else {
+                    mNumPendingFinished++;
+                }
+                
+                AuthorityInfo authority = getAuthorityLocked(op.account, op.authority,
+                        "deleteFromPending");
+                if (authority != null) {
+                    if (DEBUG) Log.v(TAG, "removing - " + authority);
+                    final int N = mPendingOperations.size();
+                    boolean morePending = false;
+                    for (int i=0; i<N; i++) {
+                        PendingOperation cur = mPendingOperations.get(i);
+                        if (cur.account.equals(op.account)
+                                && cur.authority.equals(op.authority)) {
+                            morePending = true;
+                            break;
+                        }
+                    }
+                    
+                    if (!morePending) {
+                        if (DEBUG) Log.v(TAG, "no more pending!");
+                        SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
+                        status.pending = false;
+                    }
+                }
+                
+                res = true;
+            }
+        }
+        
+        reportChange(CHANGE_PENDING);
+        return res;
+    }
+
+    public int clearPending() {
+        int num;
+        synchronized (mAuthorities) {
+            if (DEBUG) Log.v(TAG, "clearPending");
+            num = mPendingOperations.size();
+            mPendingOperations.clear();
+            final int N = mSyncStatus.size();
+            for (int i=0; i<N; i++) {
+                mSyncStatus.get(i).pending = false;
+            }
+            writePendingOperationsLocked();
+        }
+        reportChange(CHANGE_PENDING);
+        return num;
+    }
+
+    /**
+     * Return a copy of the current array of pending operations.  The
+     * PendingOperation objects are the real objects stored inside, so that
+     * they can be used with deleteFromPending().
+     */
+    public ArrayList<PendingOperation> getPendingOperations() {
+        synchronized (mAuthorities) {
+            return new ArrayList<PendingOperation>(mPendingOperations);
+        }
+    }
+    
+    /**
+     * Return the number of currently pending operations.
+     */
+    public int getPendingOperationCount() {
+        synchronized (mAuthorities) {
+            return mPendingOperations.size();
+        }
+    }
+    
+    /**
+     * Called when the set of account has changed, given the new array of
+     * active accounts.
+     */
+    public void doDatabaseCleanup(String[] accounts) {
+        synchronized (mAuthorities) {
+            if (DEBUG) Log.w(TAG, "Updating for new accounts...");
+            SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>();
+            Iterator<AccountInfo> accIt = mAccounts.values().iterator();
+            while (accIt.hasNext()) {
+                AccountInfo acc = accIt.next();
+                if (!ArrayUtils.contains(accounts, acc.account)) {
+                    // This account no longer exists...
+                    if (DEBUG) Log.w(TAG, "Account removed: " + acc.account);
+                    for (AuthorityInfo auth : acc.authorities.values()) {
+                        removing.put(auth.ident, auth);
+                    }
+                    accIt.remove();
+                }
+            }
+            
+            // Clean out all data structures.
+            int i = removing.size();
+            if (i > 0) {
+                while (i > 0) {
+                    i--;
+                    int ident = removing.keyAt(i);
+                    mAuthorities.remove(ident);
+                    int j = mSyncStatus.size();
+                    while (j > 0) {
+                        j--;
+                        if (mSyncStatus.keyAt(j) == ident) {
+                            mSyncStatus.remove(mSyncStatus.keyAt(j));
+                        }
+                    }
+                    j = mSyncHistory.size();
+                    while (j > 0) {
+                        j--;
+                        if (mSyncHistory.get(j).authorityId == ident) {
+                            mSyncHistory.remove(j);
+                        }
+                    }
+                }
+                writeAccountInfoLocked();
+                writeStatusLocked();
+                writePendingOperationsLocked();
+                writeStatisticsLocked();
+            }
+        }
+    }
+
+    /**
+     * Called when the currently active sync is changing (there can only be
+     * one at a time).  Either supply a valid ActiveSyncContext with information
+     * about the sync, or null to stop the currently active sync.
+     */
+    public void setActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
+        synchronized (mAuthorities) {
+            if (activeSyncContext != null) {
+                if (DEBUG) Log.v(TAG, "setActiveSync: account="
+                        + activeSyncContext.mSyncOperation.account
+                        + " auth=" + activeSyncContext.mSyncOperation.authority
+                        + " src=" + activeSyncContext.mSyncOperation.syncSource
+                        + " extras=" + activeSyncContext.mSyncOperation.extras);
+                if (mActiveSync != null) {
+                    Log.w(TAG, "setActiveSync called with existing active sync!");
+                }
+                AuthorityInfo authority = getAuthorityLocked(
+                        activeSyncContext.mSyncOperation.account,
+                        activeSyncContext.mSyncOperation.authority,
+                        "setActiveSync");
+                if (authority == null) {
+                    return;
+                }
+                mActiveSync = new ActiveSyncInfo(authority.ident,
+                        authority.account, authority.authority,
+                        activeSyncContext.mStartTime);
+            } else {
+                if (DEBUG) Log.v(TAG, "setActiveSync: null");
+                mActiveSync = null;
+            }
+        }
+        
+        reportChange(CHANGE_ACTIVE);
+    }
+
+    /**
+     * To allow others to send active change reports, to poke clients.
+     */
+    public void reportActiveChange() {
+        reportChange(CHANGE_ACTIVE);
+    }
+    
+    /**
+     * Note that sync has started for the given account and authority.
+     */
+    public long insertStartSyncEvent(String accountName, String authorityName,
+            long now, int source) {
+        long id;
+        synchronized (mAuthorities) {
+            if (DEBUG) Log.v(TAG, "insertStartSyncEvent: account=" + accountName
+                    + " auth=" + authorityName + " source=" + source);
+            AuthorityInfo authority = getAuthorityLocked(accountName, authorityName,
+                    "insertStartSyncEvent");
+            if (authority == null) {
+                return -1;
+            }
+            SyncHistoryItem item = new SyncHistoryItem();
+            item.authorityId = authority.ident;
+            item.historyId = mNextHistoryId++;
+            if (mNextHistoryId < 0) mNextHistoryId = 0;
+            item.eventTime = now;
+            item.source = source;
+            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);
+        }
+        
+        reportChange(CHANGE_STATUS);
+        return id;
     }
 
     public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage,
             long downstreamActivity, long upstreamActivity) {
-        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
-        db.beginTransaction();
-        try {
-            ContentValues values = new ContentValues();
-            values.put(Sync.History.ELAPSED_TIME, elapsedTime);
-            values.put(Sync.History.EVENT, Sync.History.EVENT_STOP);
-            values.put(Sync.History.MESG, resultMessage);
-            values.put(Sync.History.DOWNSTREAM_ACTIVITY, downstreamActivity);
-            values.put(Sync.History.UPSTREAM_ACTIVITY, upstreamActivity);
-
-            int count = db.update("history", values, "_id=?",
-                    new String[]{Long.toString(historyId)});
-            // We think that count should always be 1 but don't want to change this until after
-            // launch.
-            if (count > 0) {
-                int source = (int) DatabaseUtils.longForQuery(db,
-                        "SELECT source FROM history WHERE _id=" + historyId, null);
-                long eventTime = DatabaseUtils.longForQuery(db,
-                        "SELECT eventTime FROM history WHERE _id=" + historyId, null);
-                long statsId = DatabaseUtils.longForQuery(db,
-                        "SELECT stats_id FROM history WHERE _id=" + historyId, null);
-
-                createStatusRowIfNecessary(statsId);
-
-                // update the status table to reflect this sync
-                StringBuilder sb = new StringBuilder();
-                ArrayList<String> bindArgs = new ArrayList<String>();
-                sb.append("UPDATE status SET");
-                sb.append(" numSyncs=numSyncs+1");
-                sb.append(", totalElapsedTime=totalElapsedTime+" + elapsedTime);
-                switch (source) {
-                    case Sync.History.SOURCE_LOCAL:
-                        sb.append(", numSourceLocal=numSourceLocal+1");
-                        break;
-                    case Sync.History.SOURCE_POLL:
-                        sb.append(", numSourcePoll=numSourcePoll+1");
-                        break;
-                    case Sync.History.SOURCE_USER:
-                        sb.append(", numSourceUser=numSourceUser+1");
-                        break;
-                    case Sync.History.SOURCE_SERVER:
-                        sb.append(", numSourceServer=numSourceServer+1");
-                        break;
+        synchronized (mAuthorities) {
+            if (DEBUG) Log.v(TAG, "stopSyncEvent: historyId=" + historyId);
+            SyncHistoryItem item = null;
+            int i = mSyncHistory.size();
+            while (i > 0) {
+                i--;
+                item = mSyncHistory.get(i);
+                if (item.historyId == historyId) {
+                    break;
                 }
-
-                final String statsIdString = String.valueOf(statsId);
-                final long lastSyncTime = (eventTime + elapsedTime);
-                if (Sync.History.MESG_SUCCESS.equals(resultMessage)) {
-                    // - if successful, update the successful columns
-                    sb.append(", lastSuccessTime=" + lastSyncTime);
-                    sb.append(", lastSuccessSource=" + source);
-                    sb.append(", lastFailureTime=null");
-                    sb.append(", lastFailureSource=null");
-                    sb.append(", lastFailureMesg=null");
-                    sb.append(", initialFailureTime=null");
-                } else if (!Sync.History.MESG_CANCELED.equals(resultMessage)) {
-                    sb.append(", lastFailureTime=" + lastSyncTime);
-                    sb.append(", lastFailureSource=" + source);
-                    sb.append(", lastFailureMesg=?");
-                    bindArgs.add(resultMessage);
-                    long initialFailureTime = DatabaseUtils.longForQuery(db,
-                            SELECT_INITIAL_FAILURE_TIME_QUERY_STRING, 
-                            new String[]{statsIdString, String.valueOf(lastSyncTime)});
-                    sb.append(", initialFailureTime=" + initialFailureTime);
-                }
-                sb.append(" WHERE stats_id=?");
-                bindArgs.add(statsIdString);
-                db.execSQL(sb.toString(), bindArgs.toArray());
-                db.setTransactionSuccessful();
-                mContext.getContentResolver().notifyChange(Sync.History.CONTENT_URI,
-                        null /* observer */);
-                mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI,
-                        null /* observer */);
+                item = null;
             }
-        } finally {
-            db.endTransaction();
+            
+            if (item == null) {
+                Log.w(TAG, "stopSyncEvent: no history for id " + historyId);
+                return;
+            }
+            
+            item.elapsedTime = elapsedTime;
+            item.event = EVENT_STOP;
+            item.mesg = resultMessage;
+            item.downstreamActivity = downstreamActivity;
+            item.upstreamActivity = upstreamActivity;
+            
+            SyncStatusInfo status = getOrCreateSyncStatusLocked(item.authorityId);
+            
+            status.numSyncs++;
+            status.totalElapsedTime += elapsedTime;
+            switch (item.source) {
+                case SOURCE_LOCAL:
+                    status.numSourceLocal++;
+                    break;
+                case SOURCE_POLL:
+                    status.numSourcePoll++;
+                    break;
+                case SOURCE_USER:
+                    status.numSourceUser++;
+                    break;
+                case SOURCE_SERVER:
+                    status.numSourceServer++;
+                    break;
+            }
+            
+            boolean writeStatisticsNow = false;
+            int day = getCurrentDay();
+            if (mDayStats[0] == null) {
+                mDayStats[0] = new DayStats(day);
+            } else if (day != mDayStats[0].day) {
+                System.arraycopy(mDayStats, 0, mDayStats, 1, mDayStats.length-1);
+                mDayStats[0] = new DayStats(day);
+                writeStatisticsNow = true;
+            } else if (mDayStats[0] == null) {
+            }
+            final DayStats ds = mDayStats[0];
+            
+            final long lastSyncTime = (item.eventTime + elapsedTime);
+            boolean writeStatusNow = false;
+            if (MESG_SUCCESS.equals(resultMessage)) {
+                // - if successful, update the successful columns
+                if (status.lastSuccessTime == 0 || status.lastFailureTime != 0) {
+                    writeStatusNow = true;
+                }
+                status.lastSuccessTime = lastSyncTime;
+                status.lastSuccessSource = item.source;
+                status.lastFailureTime = 0;
+                status.lastFailureSource = -1;
+                status.lastFailureMesg = null;
+                status.initialFailureTime = 0;
+                ds.successCount++;
+                ds.successTime += elapsedTime;
+            } else if (!MESG_CANCELED.equals(resultMessage)) {
+                if (status.lastFailureTime == 0) {
+                    writeStatusNow = true;
+                }
+                status.lastFailureTime = lastSyncTime;
+                status.lastFailureSource = item.source;
+                status.lastFailureMesg = resultMessage;
+                if (status.initialFailureTime == 0) {
+                    status.initialFailureTime = lastSyncTime;
+                }
+                ds.failureCount++;
+                ds.failureTime += elapsedTime;
+            }
+            
+            if (writeStatusNow) {
+                writeStatusLocked();
+            } else if (!hasMessages(MSG_WRITE_STATUS)) {
+                sendMessageDelayed(obtainMessage(MSG_WRITE_STATUS),
+                        WRITE_STATUS_DELAY);
+            }
+            if (writeStatisticsNow) {
+                writeStatisticsLocked();
+            } else if (!hasMessages(MSG_WRITE_STATISTICS)) {
+                sendMessageDelayed(obtainMessage(MSG_WRITE_STATISTICS),
+                        WRITE_STATISTICS_DELAY);
+            }            
+        }
+        
+        reportChange(CHANGE_STATUS);
+    }
+
+    /**
+     * Return the currently active sync information, or null if there is no
+     * active sync.  Note that the returned object is the real, live active
+     * sync object, so be careful what you do with it.
+     */
+    public ActiveSyncInfo getActiveSync() {
+        synchronized (mAuthorities) {
+            return mActiveSync;
+        }
+    }
+    
+    /**
+     * Return an array of the current sync status for all authorities.  Note
+     * that the objects inside the array are the real, live status objects,
+     * so be careful what you do with them.
+     */
+    public ArrayList<SyncStatusInfo> getSyncStatus() {
+        synchronized (mAuthorities) {
+            final int N = mSyncStatus.size();
+            ArrayList<SyncStatusInfo> ops = new ArrayList<SyncStatusInfo>(N);
+            for (int i=0; i<N; i++) {
+                ops.add(mSyncStatus.valueAt(i));
+            }
+            return ops;
+        }
+    }
+    
+    /**
+     * 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
+     */
+    public SyncStatusInfo getStatusByAuthority(String authority) {
+        synchronized (mAuthorities) {
+            SyncStatusInfo best = null;
+            final int N = mSyncStatus.size();
+            for (int i=0; i<N; i++) {
+                SyncStatusInfo cur = mSyncStatus.get(i);
+                AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
+                if (ainfo != null && ainfo.authority.equals(authority)) {
+                    if (best == null) {
+                        best = cur;
+                    } else if (best.lastSuccessTime > cur.lastSuccessTime) {
+                        best = cur;
+                    }
+                }
+            }
+            return best;
+        }
+    }
+    
+    /**
+     * Return true if the pending status is true of any matching authorities.
+     */
+    public boolean isAuthorityPending(String account, String authority) {
+        synchronized (mAuthorities) {
+            final int N = mSyncStatus.size();
+            for (int i=0; i<N; i++) {
+                SyncStatusInfo cur = mSyncStatus.get(i);
+                AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
+                if (ainfo == null) {
+                    continue;
+                }
+                if (account != null && !ainfo.account.equals(account)) {
+                    continue;
+                }
+                if (ainfo.authority.equals(authority) && cur.pending) {
+                    return true;
+                }
+            }
+            return false;
         }
     }
 
     /**
+     * Return an array of the current sync status for all authorities.  Note
+     * that the objects inside the array are the real, live status objects,
+     * so be careful what you do with them.
+     */
+    public ArrayList<SyncHistoryItem> getSyncHistory() {
+        synchronized (mAuthorities) {
+            final int N = mSyncHistory.size();
+            ArrayList<SyncHistoryItem> items = new ArrayList<SyncHistoryItem>(N);
+            for (int i=0; i<N; i++) {
+                items.add(mSyncHistory.get(i));
+            }
+            return items;
+        }
+    }
+    
+    /**
+     * Return an array of the current per-day statistics.  Note
+     * that the objects inside the array are the real, live status objects,
+     * so be careful what you do with them.
+     */
+    public DayStats[] getDayStatistics() {
+        synchronized (mAuthorities) {
+            DayStats[] ds = new DayStats[mDayStats.length];
+            System.arraycopy(mDayStats, 0, ds, 0, ds.length);
+            return ds;
+        }
+    }
+    
+    /**
      * If sync is failing for any of the provider/accounts then determine the time at which it
      * started failing and return the earliest time over all the provider/accounts. If none are
      * failing then return 0.
      */
     public long getInitialSyncFailureTime() {
-        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
-        // Join the settings for a provider with the status so that we can easily
-        // check if each provider is enabled for syncing. We also join in the overall
-        // enabled flag ("listen_for_tickles") to each row so that we don't need to
-        // make a separate DB lookup to access it.
-        Cursor c = db.rawQuery(""
-                + "SELECT initialFailureTime, s1.value, s2.value "
-                + "FROM status "
-                + "LEFT JOIN stats ON status.stats_id=stats._id "
-                + "LEFT JOIN settings as s1 ON 'sync_provider_' || authority=s1.name "
-                + "LEFT JOIN settings as s2 ON s2.name='listen_for_tickles' "
-                + "where initialFailureTime is not null "
-                + "  AND lastFailureMesg!=" + Sync.History.ERROR_TOO_MANY_DELETIONS
-                + "  AND lastFailureMesg!=" + Sync.History.ERROR_AUTHENTICATION
-                + "  AND lastFailureMesg!=" + Sync.History.ERROR_SYNC_ALREADY_IN_PROGRESS
-                + "  AND authority!='subscribedfeeds' "
-                + " ORDER BY initialFailureTime", null);
+        synchronized (mAuthorities) {
+            if (!mListenForTickles) {
+                return 0;
+            }
+            
+            long oldest = 0;
+            int i = mSyncStatus.size();
+            while (i > 0) {
+                i--;
+                SyncStatusInfo stats = mSyncStatus.valueAt(i);
+                AuthorityInfo authority = mAuthorities.get(stats.authorityId);
+                if (authority != null && authority.enabled) {
+                    if (oldest == 0 || stats.initialFailureTime < oldest) {
+                        oldest = stats.initialFailureTime;
+                    }
+                }
+            }
+            
+            return oldest;
+        }
+    }
+    
+    private int getCurrentDay() {
+        mCal.setTimeInMillis(System.currentTimeMillis());
+        final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR);
+        if (mYear != mCal.get(Calendar.YEAR)) {
+            mYear = mCal.get(Calendar.YEAR);
+            mCal.clear();
+            mCal.set(Calendar.YEAR, mYear);
+            mYearInDays = (int)(mCal.getTimeInMillis()/86400000);
+        }
+        return dayOfYear + mYearInDays;
+    }
+    
+    /**
+     * 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 tag If non-null, this will be used in a log message if the
+     * requested authority does not exist.
+     */
+    private AuthorityInfo getAuthorityLocked(String accountName, String authorityName,
+            String tag) {
+        AccountInfo account = mAccounts.get(accountName);
+        if (account == null) {
+            if (tag != null) {
+                Log.w(TAG, tag + ": unknown account " + accountName);
+            }
+            return null;
+        }
+        AuthorityInfo authority = account.authorities.get(authorityName);
+        if (authority == null) {
+            if (tag != null) {
+                Log.w(TAG, tag + ": unknown authority " + authorityName);
+            }
+            return null;
+        }
+        
+        return authority;
+    }
+    
+    private AuthorityInfo getOrCreateAuthorityLocked(String accountName,
+            String authorityName, int ident, boolean doWrite) {
+        AccountInfo account = mAccounts.get(accountName);
+        if (account == null) {
+            account = new AccountInfo(accountName);
+            mAccounts.put(accountName, account);
+        }
+        AuthorityInfo authority = account.authorities.get(authorityName);
+        if (authority == null) {
+            if (ident < 0) {
+                // Look for a new identifier for this authority.
+                final int N = mAuthorities.size();
+                ident = 0;
+                for (int i=0; i<N; i++) {
+                    if (mAuthorities.valueAt(i).ident > ident) {
+                        break;
+                    }
+                    ident++;
+                }
+            }
+            authority = new AuthorityInfo(accountName, authorityName, ident);
+            account.authorities.put(authorityName, authority);
+            mAuthorities.put(ident, authority);
+            if (doWrite) {
+                writeAccountInfoLocked();
+            }
+        }
+        
+        return authority;
+    }
+    
+    private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) {
+        SyncStatusInfo status = mSyncStatus.get(authorityId);
+        if (status == null) {
+            status = new SyncStatusInfo(authorityId);
+            mSyncStatus.put(authorityId, status);
+        }
+        return status;
+    }
+    
+    /**
+     * Read all account information back in to the initial engine state.
+     */
+    private void readAccountInfoLocked() {
+        FileInputStream fis = null;
         try {
+            fis = mAccountInfoFile.openRead();
+            if (DEBUG_FILE) Log.v(TAG, "Reading " + mAccountInfoFile.getBaseFile());
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(fis, null);
+            int eventType = parser.getEventType();
+            while (eventType != XmlPullParser.START_TAG) {
+                eventType = parser.next();
+            }
+            String tagName = parser.getName();
+            if ("accounts".equals(tagName)) {
+                String listen = parser.getAttributeValue(
+                        null, "listen-for-tickles");
+                mListenForTickles = listen == null
+                            || Boolean.parseBoolean(listen);
+                eventType = parser.next();
+                do {
+                    if (eventType == XmlPullParser.START_TAG
+                            && parser.getDepth() == 2) {
+                        tagName = parser.getName();
+                        if ("authority".equals(tagName)) {
+                            int id = -1;
+                            try {
+                                id = Integer.parseInt(parser.getAttributeValue(
+                                        null, "id"));
+                            } catch (NumberFormatException e) {
+                            } catch (NullPointerException e) {
+                            }
+                            if (id >= 0) {
+                                String accountName = parser.getAttributeValue(
+                                        null, "account");
+                                String authorityName = parser.getAttributeValue(
+                                        null, "authority");
+                                String enabled = parser.getAttributeValue(
+                                        null, "enabled");
+                                AuthorityInfo authority = mAuthorities.get(id); 
+                                if (DEBUG_FILE) Log.v(TAG, "Adding authority: account="
+                                        + accountName + " auth=" + authorityName
+                                        + " enabled=" + enabled);
+                                if (authority == null) {
+                                    if (DEBUG_FILE) Log.v(TAG, "Creating entry");
+                                    authority = getOrCreateAuthorityLocked(
+                                        accountName, authorityName, id, false);
+                                }
+                                if (authority != null) {
+                                    authority.enabled = enabled == null
+                                            || Boolean.parseBoolean(enabled);
+                                } else {
+                                    Log.w(TAG, "Failure adding authority: account="
+                                            + accountName + " auth=" + authorityName
+                                            + " enabled=" + enabled);
+                                }
+                            }
+                        }
+                    }
+                    eventType = parser.next();
+                } while (eventType != XmlPullParser.END_DOCUMENT);
+            }
+        } catch (XmlPullParserException e) {
+            Log.w(TAG, "Error reading accounts", e);
+        } catch (java.io.IOException e) {
+            if (fis == null) Log.i(TAG, "No initial accounts");
+            else Log.w(TAG, "Error reading accounts", e);
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (java.io.IOException e1) {
+                }
+            }
+        }
+    }
+    
+    /**
+     * Write all account information to the account file.
+     */
+    private void writeAccountInfoLocked() {
+        if (DEBUG_FILE) Log.v(TAG, "Writing new " + mAccountInfoFile.getBaseFile());
+        FileOutputStream fos = null;
+        
+        try {
+            fos = mAccountInfoFile.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, "accounts");
+            if (!mListenForTickles) {
+                out.attribute(null, "listen-for-tickles", "false");
+            }
+            
+            final int N = mAuthorities.size();
+            for (int i=0; i<N; i++) {
+                AuthorityInfo authority = mAuthorities.get(i);
+                out.startTag(null, "authority");
+                out.attribute(null, "id", Integer.toString(authority.ident));
+                out.attribute(null, "account", authority.account);
+                out.attribute(null, "authority", authority.authority);
+                if (!authority.enabled) {
+                    out.attribute(null, "enabled", "false");
+                }
+                out.endTag(null, "authority");
+            }
+            
+            out.endTag(null, "accounts");
+            
+            out.endDocument();
+            
+            mAccountInfoFile.finishWrite(fos);
+        } catch (java.io.IOException e1) {
+            Log.w(TAG, "Error writing accounts", e1);
+            if (fos != null) {
+                mAccountInfoFile.failWrite(fos);
+            }
+        }
+    }
+    
+    static int getIntColumn(Cursor c, String name) {
+        return c.getInt(c.getColumnIndex(name));
+    }
+    
+    static long getLongColumn(Cursor c, String name) {
+        return c.getLong(c.getColumnIndex(name));
+    }
+    
+    /**
+     * Load sync engine state from the old syncmanager database, and then
+     * erase it.  Note that we don't deal with pending operations, active
+     * sync, or history.
+     */
+    private void readLegacyAccountInfoLocked() {
+        // Look for old database to initialize from.
+        File file = mContext.getDatabasePath("syncmanager.db");
+        if (!file.exists()) {
+            return;
+        }
+        String path = file.getPath();
+        SQLiteDatabase db = null;
+        try {
+            db = SQLiteDatabase.openDatabase(path, null,
+                    SQLiteDatabase.OPEN_READONLY);
+        } catch (SQLiteException e) {
+        }
+        
+        if (db != null) {
+            // Copy in all of the status information, as well as accounts.
+            if (DEBUG_FILE) Log.v(TAG, "Reading legacy sync accounts db");
+            SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+            qb.setTables("stats, status");
+            HashMap<String,String> map = new HashMap<String,String>();
+            map.put("_id", "status._id as _id");
+            map.put("account", "stats.account as account");
+            map.put("authority", "stats.authority as authority");
+            map.put("totalElapsedTime", "totalElapsedTime");
+            map.put("numSyncs", "numSyncs");
+            map.put("numSourceLocal", "numSourceLocal");
+            map.put("numSourcePoll", "numSourcePoll");
+            map.put("numSourceServer", "numSourceServer");
+            map.put("numSourceUser", "numSourceUser");
+            map.put("lastSuccessSource", "lastSuccessSource");
+            map.put("lastSuccessTime", "lastSuccessTime");
+            map.put("lastFailureSource", "lastFailureSource");
+            map.put("lastFailureTime", "lastFailureTime");
+            map.put("lastFailureMesg", "lastFailureMesg");
+            map.put("pending", "pending");
+            qb.setProjectionMap(map);
+            qb.appendWhere("stats._id = status.stats_id");
+            Cursor c = qb.query(db, null, null, null, null, null, null);
             while (c.moveToNext()) {
-                // these settings default to true, so if they are null treat them as enabled
-                final String providerEnabledString = c.getString(1);
-                if (providerEnabledString != null && !Boolean.parseBoolean(providerEnabledString)) {
-                    continue;
+                String accountName = c.getString(c.getColumnIndex("account"));
+                String authorityName = c.getString(c.getColumnIndex("authority"));
+                AuthorityInfo authority = this.getOrCreateAuthorityLocked(
+                        accountName, authorityName, -1, false);
+                if (authority != null) {
+                    int i = mSyncStatus.size();
+                    boolean found = false;
+                    SyncStatusInfo st = null;
+                    while (i > 0) {
+                        i--;
+                        st = mSyncStatus.get(i);
+                        if (st.authorityId == authority.ident) {
+                            found = true;
+                            break;
+                        }
+                    }
+                    if (!found) {
+                        st = new SyncStatusInfo(authority.ident);
+                        mSyncStatus.put(authority.ident, st);
+                    }
+                    st.totalElapsedTime = getLongColumn(c, "totalElapsedTime");
+                    st.numSyncs = getIntColumn(c, "numSyncs");
+                    st.numSourceLocal = getIntColumn(c, "numSourceLocal");
+                    st.numSourcePoll = getIntColumn(c, "numSourcePoll");
+                    st.numSourceServer = getIntColumn(c, "numSourceServer");
+                    st.numSourceUser = getIntColumn(c, "numSourceUser");
+                    st.lastSuccessSource = getIntColumn(c, "lastSuccessSource");
+                    st.lastSuccessTime = getLongColumn(c, "lastSuccessTime");
+                    st.lastFailureSource = getIntColumn(c, "lastFailureSource");
+                    st.lastFailureTime = getLongColumn(c, "lastFailureTime");
+                    st.lastFailureMesg = c.getString(c.getColumnIndex("lastFailureMesg"));
+                    st.pending = getIntColumn(c, "pending") != 0;
                 }
-                final String allEnabledString = c.getString(2);
-                if (allEnabledString != null && !Boolean.parseBoolean(allEnabledString)) {
-                    continue;
-                }
-                return c.getLong(0);
             }
-        } finally {
+            
             c.close();
-        }
-        return 0;
-    }
-
-    private void createStatusRowIfNecessary(long statsId) {
-        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
-        boolean statusExists = 0 != DatabaseUtils.longForQuery(db,
-                "SELECT count(*) FROM status WHERE stats_id=" + statsId, null);
-        if (!statusExists) {
-            ContentValues values = new ContentValues();
-            values.put("stats_id", statsId);
-            db.insert("status", null, values);
-        }
-    }
-
-    private long createStatsRowIfNecessary(String account, String authority) {
-        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
-        StringBuilder where = new StringBuilder();
-        where.append(Sync.Stats.ACCOUNT + "= ?");
-        where.append(" and " + Sync.Stats.AUTHORITY + "= ?");
-        Cursor cursor = query(Sync.Stats.CONTENT_URI,
-                Sync.Stats.SYNC_STATS_PROJECTION,
-                where.toString(), new String[] { account, authority },
-                null /* order */);
-        try {
-            long id;
-            if (cursor.moveToFirst()) {
-                id = cursor.getLong(cursor.getColumnIndexOrThrow(Sync.Stats._ID));
-            } else {
-                ContentValues values = new ContentValues();
-                values.put(Sync.Stats.ACCOUNT, account);
-                values.put(Sync.Stats.AUTHORITY, authority);
-                id = db.insert("stats", null, values);
+            
+            // Retrieve the settings.
+            qb = new SQLiteQueryBuilder();
+            qb.setTables("settings");
+            c = qb.query(db, null, null, null, null, null, null);
+            while (c.moveToNext()) {
+                String name = c.getString(c.getColumnIndex("name"));
+                String value = c.getString(c.getColumnIndex("value"));
+                if (name == null) continue;
+                if (name.equals("listen_for_tickles")) {
+                    setListenForNetworkTickles(value == null
+                            || Boolean.parseBoolean(value));
+                } else if (name.startsWith("sync_provider_")) {
+                    String provider = name.substring("sync_provider_".length(),
+                            name.length());
+                    setSyncProviderAutomatically(null, provider,
+                            value == null || Boolean.parseBoolean(value));
+                }
             }
-            return id;
+            
+            c.close();
+            
+            db.close();
+            
+            writeAccountInfoLocked();
+            writeStatusLocked();
+            (new File(path)).delete();
+        }
+    }
+    
+    public static final int STATUS_FILE_END = 0;
+    public static final int STATUS_FILE_ITEM = 100;
+    
+    /**
+     * Read all sync status back in to the initial engine state.
+     */
+    private void readStatusLocked() {
+        if (DEBUG_FILE) Log.v(TAG, "Reading " + mStatusFile.getBaseFile());
+        try {
+            byte[] data = mStatusFile.readFully();
+            Parcel in = Parcel.obtain();
+            in.unmarshall(data, 0, data.length);
+            in.setDataPosition(0);
+            int token;
+            while ((token=in.readInt()) != STATUS_FILE_END) {
+                if (token == STATUS_FILE_ITEM) {
+                    SyncStatusInfo status = new SyncStatusInfo(in);
+                    if (mAuthorities.indexOfKey(status.authorityId) >= 0) {
+                        status.pending = false;
+                        if (DEBUG_FILE) Log.v(TAG, "Adding status for id "
+                                + status.authorityId);
+                        mSyncStatus.put(status.authorityId, status);
+                    }
+                } else {
+                    // Ooops.
+                    Log.w(TAG, "Unknown status token: " + token);
+                    break;
+                }
+            }
+        } catch (java.io.IOException e) {
+            Log.i(TAG, "No initial status");
+        }
+    }
+    
+    /**
+     * Write all sync status to the sync status file.
+     */
+    private void writeStatusLocked() {
+        if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatusFile.getBaseFile());
+        
+        // The file is being written, so we don't need to have a scheduled
+        // write until the next change.
+        removeMessages(MSG_WRITE_STATUS);
+        
+        FileOutputStream fos = null;
+        try {
+            fos = mStatusFile.startWrite();
+            Parcel out = Parcel.obtain();
+            final int N = mSyncStatus.size();
+            for (int i=0; i<N; i++) {
+                SyncStatusInfo status = mSyncStatus.valueAt(i);
+                out.writeInt(STATUS_FILE_ITEM);
+                status.writeToParcel(out, 0);
+            }
+            out.writeInt(STATUS_FILE_END);
+            fos.write(out.marshall());
+            out.recycle();
+            
+            mStatusFile.finishWrite(fos);
+        } catch (java.io.IOException e1) {
+            Log.w(TAG, "Error writing status", e1);
+            if (fos != null) {
+                mStatusFile.failWrite(fos);
+            }
+        }
+    }
+    
+    public static final int PENDING_OPERATION_VERSION = 1;
+    
+    /**
+     * Read all pending operations back in to the initial engine state.
+     */
+    private void readPendingOperationsLocked() {
+        if (DEBUG_FILE) Log.v(TAG, "Reading " + mPendingFile.getBaseFile());
+        try {
+            byte[] data = mPendingFile.readFully();
+            Parcel in = Parcel.obtain();
+            in.unmarshall(data, 0, data.length);
+            in.setDataPosition(0);
+            final int SIZE = in.dataSize();
+            while (in.dataPosition() < SIZE) {
+                int version = in.readInt();
+                if (version != PENDING_OPERATION_VERSION) {
+                    Log.w(TAG, "Unknown pending operation version "
+                            + version + "; dropping all ops");
+                    break;
+                }
+                int authorityId = in.readInt();
+                int syncSource = in.readInt();
+                byte[] flatExtras = in.createByteArray();
+                AuthorityInfo authority = mAuthorities.get(authorityId);
+                if (authority != null) {
+                    Bundle extras = null;
+                    if (flatExtras != null) {
+                        extras = unflattenBundle(flatExtras);
+                    }
+                    PendingOperation op = new PendingOperation(
+                            authority.account, syncSource,
+                            authority.authority, extras);
+                    op.authorityId = authorityId;
+                    op.flatExtras = flatExtras;
+                    if (DEBUG_FILE) Log.v(TAG, "Adding pending op: account=" + op.account
+                            + " auth=" + op.authority
+                            + " src=" + op.syncSource
+                            + " extras=" + op.extras);
+                    mPendingOperations.add(op);
+                }
+            }
+        } catch (java.io.IOException e) {
+            Log.i(TAG, "No initial pending operations");
+        }
+    }
+    
+    private void writePendingOperationLocked(PendingOperation op, Parcel out) {
+        out.writeInt(PENDING_OPERATION_VERSION);
+        out.writeInt(op.authorityId);
+        out.writeInt(op.syncSource);
+        if (op.flatExtras == null && op.extras != null) {
+            op.flatExtras = flattenBundle(op.extras);
+        }
+        out.writeByteArray(op.flatExtras);
+    }
+    
+    /**
+     * 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 (DEBUG_FILE) Log.v(TAG, "Truncating " + mPendingFile.getBaseFile());
+                mPendingFile.truncate();
+                return;
+            }
+            
+            if (DEBUG_FILE) Log.v(TAG, "Writing new " + mPendingFile.getBaseFile());
+            fos = mPendingFile.startWrite();
+        
+            Parcel out = Parcel.obtain();
+            for (int i=0; i<N; i++) {
+                PendingOperation op = mPendingOperations.get(i);
+                writePendingOperationLocked(op, out);
+            }
+            fos.write(out.marshall());
+            out.recycle();
+            
+            mPendingFile.finishWrite(fos);
+        } catch (java.io.IOException e1) {
+            Log.w(TAG, "Error writing pending operations", e1);
+            if (fos != null) {
+                mPendingFile.failWrite(fos);
+            }
+        }
+    }
+    
+    /**
+     * Append the given operation to the pending ops file; if unable to,
+     * write all pending ops.
+     */
+    private void appendPendingOperationLocked(PendingOperation op) {
+        if (DEBUG_FILE) Log.v(TAG, "Appending to " + mPendingFile.getBaseFile());
+        FileOutputStream fos = null;
+        try {
+            fos = mPendingFile.openAppend();
+        } catch (java.io.IOException e) {
+            if (DEBUG_FILE) Log.v(TAG, "Failed append; writing full file");
+            writePendingOperationsLocked();
+            return;
+        }
+        
+        try {
+            Parcel out = Parcel.obtain();
+            writePendingOperationLocked(op, out);
+            fos.write(out.marshall());
+            out.recycle();
+        } catch (java.io.IOException e1) {
+            Log.w(TAG, "Error writing pending operations", e1);
         } finally {
-            cursor.close();
+            try {
+                fos.close();
+            } catch (java.io.IOException e2) {
+            }
+        }
+    }
+    
+    static private byte[] flattenBundle(Bundle bundle) {
+        byte[] flatData = null;
+        Parcel parcel = Parcel.obtain();
+        try {
+            bundle.writeToParcel(parcel, 0);
+            flatData = parcel.marshall();
+        } finally {
+            parcel.recycle();
+        }
+        return flatData;
+    }
+    
+    static private Bundle unflattenBundle(byte[] flatData) {
+        Bundle bundle;
+        Parcel parcel = Parcel.obtain();
+        try {
+            parcel.unmarshall(flatData, 0, flatData.length);
+            parcel.setDataPosition(0);
+            bundle = parcel.readBundle();
+        } catch (RuntimeException e) {
+            // A RuntimeException is thrown if we were unable to parse the parcel.
+            // Create an empty parcel in this case.
+            bundle = new Bundle();
+        } finally {
+            parcel.recycle();
+        }
+        return bundle;
+    }
+    
+    public static final int STATISTICS_FILE_END = 0;
+    public static final int STATISTICS_FILE_ITEM_OLD = 100;
+    public static final int STATISTICS_FILE_ITEM = 101;
+    
+    /**
+     * Read all sync statistics back in to the initial engine state.
+     */
+    private void readStatisticsLocked() {
+        try {
+            byte[] data = mStatisticsFile.readFully();
+            Parcel in = Parcel.obtain();
+            in.unmarshall(data, 0, data.length);
+            in.setDataPosition(0);
+            int token;
+            int index = 0;
+            while ((token=in.readInt()) != STATISTICS_FILE_END) {
+                if (token == STATISTICS_FILE_ITEM
+                        || token == STATISTICS_FILE_ITEM_OLD) {
+                    int day = in.readInt();
+                    if (token == STATISTICS_FILE_ITEM_OLD) {
+                        day = day - 2009 + 14245;  // Magic!
+                    }
+                    DayStats ds = new DayStats(day);
+                    ds.successCount = in.readInt();
+                    ds.successTime = in.readLong();
+                    ds.failureCount = in.readInt();
+                    ds.failureTime = in.readLong();
+                    if (index < mDayStats.length) {
+                        mDayStats[index] = ds;
+                        index++;
+                    }
+                } else {
+                    // Ooops.
+                    Log.w(TAG, "Unknown stats token: " + token);
+                    break;
+                }
+            }
+        } catch (java.io.IOException e) {
+            Log.i(TAG, "No initial statistics");
+        }
+    }
+    
+    /**
+     * Write all sync statistics to the sync status file.
+     */
+    private void writeStatisticsLocked() {
+        if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatisticsFile.getBaseFile());
+        
+        // The file is being written, so we don't need to have a scheduled
+        // write until the next change.
+        removeMessages(MSG_WRITE_STATISTICS);
+        
+        FileOutputStream fos = null;
+        try {
+            fos = mStatisticsFile.startWrite();
+            Parcel out = Parcel.obtain();
+            final int N = mDayStats.length;
+            for (int i=0; i<N; i++) {
+                DayStats ds = mDayStats[i];
+                if (ds == null) {
+                    break;
+                }
+                out.writeInt(STATISTICS_FILE_ITEM);
+                out.writeInt(ds.day);
+                out.writeInt(ds.successCount);
+                out.writeLong(ds.successTime);
+                out.writeInt(ds.failureCount);
+                out.writeLong(ds.failureTime);
+            }
+            out.writeInt(STATISTICS_FILE_END);
+            fos.write(out.marshall());
+            out.recycle();
+            
+            mStatisticsFile.finishWrite(fos);
+        } catch (java.io.IOException e1) {
+            Log.w(TAG, "Error writing stats", e1);
+            if (fos != null) {
+                mStatisticsFile.failWrite(fos);
+            }
         }
     }
 }
diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java
index 63f6dff..23c0a7bf 100644
--- a/core/java/android/os/RemoteCallbackList.java
+++ b/core/java/android/os/RemoteCallbackList.java
@@ -49,25 +49,35 @@
 public class RemoteCallbackList<E extends IInterface> {
     /*package*/ HashMap<IBinder, Callback> mCallbacks
             = new HashMap<IBinder, Callback>();
-    private IInterface[] mActiveBroadcast;
+    private Object[] mActiveBroadcast;
     private boolean mKilled = false;
     
     private final class Callback implements IBinder.DeathRecipient {
         final E mCallback;
+        final Object mCookie;
         
-        Callback(E callback) {
+        Callback(E callback, Object cookie) {
             mCallback = callback;
+            mCookie = cookie;
         }
         
         public void binderDied() {
             synchronized (mCallbacks) {
                 mCallbacks.remove(mCallback.asBinder());
             }
-            onCallbackDied(mCallback);
+            onCallbackDied(mCallback, mCookie);
         }
     }
     
     /**
+     * Simple version of {@link RemoteCallbackList#register(E, Object)}
+     * that does not take a cookie object.
+     */
+    public boolean register(E callback) {
+        return register(callback, null);
+    }
+    
+    /**
      * Add a new callback to the list.  This callback will remain in the list
      * until a corresponding call to {@link #unregister} or its hosting process
      * goes away.  If the callback was already registered (determined by
@@ -81,6 +91,8 @@
      * Most services will want to check for null before calling this with
      * an object given from a client, so that clients can't crash the
      * service with bad data.
+     * @param cookie Optional additional data to be associated with this
+     * callback.
      * 
      * @return Returns true if the callback was successfully added to the list.
      * Returns false if it was not added, either because {@link #kill} had
@@ -90,14 +102,14 @@
      * @see #kill
      * @see #onCallbackDied
      */
-    public boolean register(E callback) {
+    public boolean register(E callback, Object cookie) {
         synchronized (mCallbacks) {
             if (mKilled) {
                 return false;
             }
             IBinder binder = callback.asBinder();
             try {
-                Callback cb = new Callback(callback);
+                Callback cb = new Callback(callback, cookie);
                 binder.linkToDeath(cb, 0);
                 mCallbacks.put(binder, cb);
                 return true;
@@ -154,17 +166,28 @@
     }
     
     /**
+     * Old version of {@link #onCallbackDied(E, Object)} that
+     * does not provide a cookie.
+     */
+    public void onCallbackDied(E callback) {
+    }
+    
+    /**
      * Called when the process hosting a callback in the list has gone away.
-     * The default implementation does nothing.
+     * The default implementation calls {@link #onCallbackDied(E)}
+     * for backwards compatibility.
      * 
      * @param callback The callback whose process has died.  Note that, since
      * its process has died, you can not make any calls on to this interface.
      * You can, however, retrieve its IBinder and compare it with another
      * IBinder to see if it is the same object.
+     * @param cookie The cookie object original provided to
+     * {@link #register(E, Object)}.
      * 
      * @see #register
      */
-    public void onCallbackDied(E callback) {
+    public void onCallbackDied(E callback, Object cookie) {
+        onCallbackDied(callback);
     }
     
     /**
@@ -203,13 +226,13 @@
             if (N <= 0) {
                 return 0;
             }
-            IInterface[] active = mActiveBroadcast;
+            Object[] active = mActiveBroadcast;
             if (active == null || active.length < N) {
-                mActiveBroadcast = active = new IInterface[N];
+                mActiveBroadcast = active = new Object[N];
             }
             int i=0;
             for (Callback cb : mCallbacks.values()) {
-                active[i++] = cb.mCallback;
+                active[i++] = cb;
             }
             return i;
         }
@@ -237,7 +260,17 @@
      * @see #beginBroadcast
      */
     public E getBroadcastItem(int index) {
-        return (E)mActiveBroadcast[index];
+        return ((Callback)mActiveBroadcast[index]).mCallback;
+    }
+    
+    /**
+     * Retrieve the cookie associated with the item
+     * returned by {@link #getBroadcastItem(int)}.
+     * 
+     * @see #getBroadcastItem
+     */
+    public Object getBroadcastCookie(int index) {
+        return ((Callback)mActiveBroadcast[index]).mCookie;
     }
     
     /**
@@ -248,7 +281,7 @@
      * @see #beginBroadcast
      */
     public void finishBroadcast() {
-        IInterface[] active = mActiveBroadcast;
+        Object[] active = mActiveBroadcast;
         if (active != null) {
             final int N = active.length;
             for (int i=0; i<N; i++) {
diff --git a/core/java/android/provider/Sync.java b/core/java/android/provider/Sync.java
deleted file mode 100644
index 628852f..0000000
--- a/core/java/android/provider/Sync.java
+++ /dev/null
@@ -1,633 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.provider;
-
-import android.content.ContentQueryMap;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Handler;
-
-import java.util.Map;
-
-/**
- * The Sync provider stores information used in managing the syncing of the device,
- * including the history and pending syncs.
- * 
- * @hide
- */
-public final class Sync {
-    // utility class
-    private Sync() {}
-
-    /**
-     * The content url for this provider.
-     */
-    public static final Uri CONTENT_URI = Uri.parse("content://sync");
-
-    /**
-     * Columns from the stats table.
-     */
-    public interface StatsColumns {
-        /**
-         * The sync account.
-         * <P>Type: TEXT</P>
-         */
-        public static final String ACCOUNT = "account";
-
-        /**
-         * The content authority (contacts, calendar, etc.).
-         * <P>Type: TEXT</P>
-         */
-        public static final String AUTHORITY = "authority";
-    }
-
-    /**
-     * Provides constants and utility methods to access and use the stats table.
-     */
-    public static final class Stats implements BaseColumns, StatsColumns {
-
-        // utility class
-        private Stats() {}
-
-        /**
-         * The content url for this table.
-         */
-        public static final Uri CONTENT_URI =
-                Uri.parse("content://sync/stats");
-
-        /** Projection for the _id column in the stats table. */
-        public static final String[] SYNC_STATS_PROJECTION = {_ID};
-    }
-
-    /**
-     * Columns from the history table.
-     */
-    public interface HistoryColumns {
-        /**
-         * The ID of the stats row corresponding to this event.
-         * <P>Type: INTEGER</P>
-         */
-        public static final String STATS_ID = "stats_id";
-
-        /**
-         * The source of the sync event (LOCAL, POLL, USER, SERVER).
-         * <P>Type: INTEGER</P>
-         */
-        public static final String SOURCE = "source";
-
-        /**
-         * The type of sync event (START, STOP).
-         * <P>Type: INTEGER</P>
-         */
-        public static final String EVENT = "event";
-
-        /**
-         * The time of the event.
-         * <P>Type: INTEGER</P>
-         */
-        public static final String EVENT_TIME = "eventTime";
-
-        /**
-         * How long this event took. This is only valid if the EVENT is EVENT_STOP.
-         * <P>Type: INTEGER</P>
-         */
-        public static final String ELAPSED_TIME = "elapsedTime";
-
-        /**
-         * Any additional message associated with this event.
-         * <P>Type: TEXT</P>
-         */
-        public static final String MESG = "mesg";
-
-        /**
-         * How much activity was performed sending data to the server. This is sync adapter
-         * specific, but usually is something like how many record update/insert/delete attempts
-         * were carried out. This is only valid if the EVENT is EVENT_STOP.
-         * <P>Type: INTEGER</P>
-         */
-        public static final String UPSTREAM_ACTIVITY = "upstreamActivity";
-
-        /**
-         * How much activity was performed while receiving data from the server.
-         * This is sync adapter specific, but usually is something like how many
-         * records were received from the server. This is only valid if the
-         * EVENT is EVENT_STOP.
-         * <P>Type: INTEGER</P>
-         */
-        public static final String DOWNSTREAM_ACTIVITY = "downstreamActivity";
-    }
-
-    /**
-     * Columns from the history table.
-     */
-    public interface StatusColumns {
-        /**
-         * How many syncs were completed for this account and authority.
-         * <P>Type: INTEGER</P>
-         */
-        public static final String NUM_SYNCS = "numSyncs";
-
-        /**
-         * How long all the events for this account and authority took.
-         * <P>Type: INTEGER</P>
-         */
-        public static final String TOTAL_ELAPSED_TIME = "totalElapsedTime";
-
-        /**
-         * The number of syncs with SOURCE_POLL.
-         * <P>Type: INTEGER</P>
-         */
-        public static final String NUM_SOURCE_POLL = "numSourcePoll";
-
-        /**
-         * The number of syncs with SOURCE_SERVER.
-         * <P>Type: INTEGER</P>
-         */
-        public static final String NUM_SOURCE_SERVER = "numSourceServer";
-
-        /**
-         * The number of syncs with SOURCE_LOCAL.
-         * <P>Type: INTEGER</P>
-         */
-        public static final String NUM_SOURCE_LOCAL = "numSourceLocal";
-
-        /**
-         * The number of syncs with SOURCE_USER.
-         * <P>Type: INTEGER</P>
-         */
-        public static final String NUM_SOURCE_USER = "numSourceUser";
-
-        /**
-         * The time in ms that the last successful sync ended. Will be null if
-         * there are no successful syncs. A successful sync is defined as one having
-         * MESG=MESG_SUCCESS.
-         * <P>Type: INTEGER</P>
-         */
-        public static final String LAST_SUCCESS_TIME = "lastSuccessTime";
-
-        /**
-         * The SOURCE of the last successful sync. Will be null if
-         * there are no successful syncs. A successful sync is defined
-         * as one having MESG=MESG_SUCCESS.
-         * <P>Type: INTEGER</P>
-         */
-        public static final String LAST_SUCCESS_SOURCE = "lastSuccessSource";
-
-        /**
-         * The end time in ms of the last sync that failed since the last successful sync.
-         * Will be null if there are no syncs or if the last one succeeded. A failed
-         * sync is defined as one where MESG isn't MESG_SUCCESS or MESG_CANCELED.
-         * <P>Type: INTEGER</P>
-         */
-        public static final String LAST_FAILURE_TIME = "lastFailureTime";
-
-        /**
-         * The SOURCE of the last sync that failed since the last successful sync.
-         * Will be null if there are no syncs or if the last one succeeded. A failed
-         * sync is defined as one where MESG isn't MESG_SUCCESS or MESG_CANCELED.
-         * <P>Type: INTEGER</P>
-         */
-        public static final String LAST_FAILURE_SOURCE = "lastFailureSource";
-
-        /**
-         * The MESG of the last sync that failed since the last successful sync.
-         * Will be null if there are no syncs or if the last one succeeded. A failed
-         * sync is defined as one where MESG isn't MESG_SUCCESS or MESG_CANCELED.
-         * <P>Type: STRING</P>
-         */
-        public static final String LAST_FAILURE_MESG = "lastFailureMesg";
-
-        /**
-         * Is set to 1 if a sync is pending, 0 if not.
-         * <P>Type: INTEGER</P>
-         */
-        public static final String PENDING = "pending";
-    }
-
-    /**
-     * Provides constants and utility methods to access and use the history
-     * table.
-     */
-    public static class History implements BaseColumns,
-                                                 StatsColumns,
-                                                 HistoryColumns {
-
-        /**
-         * The content url for this table.
-         */
-        public static final Uri CONTENT_URI =
-                Uri.parse("content://sync/history");
-
-        /** Enum value for a sync start event. */
-        public static final int EVENT_START = 0;
-
-        /** Enum value for a sync stop event. */
-        public static final int EVENT_STOP = 1;
-
-        // TODO: i18n -- grab these out of resources.
-        /** String names for the sync event types. */
-        public static final String[] EVENTS = { "START", "STOP" };
-
-        /** Enum value for a server-initiated sync. */
-        public static final int SOURCE_SERVER = 0;
-
-        /** 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)
-         */
-        public static final int SOURCE_POLL = 2;
-
-        /** Enum value for a user-initiated sync. */
-        public static final int SOURCE_USER = 3;
-
-        // TODO: i18n -- grab these out of resources.
-        /** String names for the sync source types. */
-        public static final String[] SOURCES = { "SERVER",
-                                                 "LOCAL",
-                                                 "POLL",
-                                                 "USER" };
-
-        // Error types
-        public static final int ERROR_SYNC_ALREADY_IN_PROGRESS = 1;
-        public static final int ERROR_AUTHENTICATION = 2;
-        public static final int ERROR_IO = 3;
-        public static final int ERROR_PARSE = 4;
-        public static final int ERROR_CONFLICT = 5;
-        public static final int ERROR_TOO_MANY_DELETIONS = 6;
-        public static final int ERROR_TOO_MANY_RETRIES = 7;
-        public static final int ERROR_INTERNAL = 8;
-
-        // The MESG column will contain one of these or one of the Error types.
-        public static final String MESG_SUCCESS = "success";
-        public static final String MESG_CANCELED = "canceled";
-
-        private static final String FINISHED_SINCE_WHERE_CLAUSE = EVENT + "=" + EVENT_STOP
-                + " AND " + EVENT_TIME + ">? AND " + ACCOUNT + "=? AND " + AUTHORITY + "=?";
-
-        public static String mesgToString(String mesg) {
-            if (MESG_SUCCESS.equals(mesg)) return mesg;
-            if (MESG_CANCELED.equals(mesg)) return mesg;
-            switch (Integer.parseInt(mesg)) {
-                case ERROR_SYNC_ALREADY_IN_PROGRESS: return "already in progress";
-                case ERROR_AUTHENTICATION: return "bad authentication";
-                case ERROR_IO: return "network error";
-                case ERROR_PARSE: return "parse error";
-                case ERROR_CONFLICT: return "conflict detected";
-                case ERROR_TOO_MANY_DELETIONS: return "too many deletions";
-                case ERROR_TOO_MANY_RETRIES: return "too many retries";
-                case ERROR_INTERNAL: return "internal error";
-                default: return "unknown error";
-            }
-        }
-
-        // utility class
-        private History() {}
-
-        /**
-         * returns a cursor that queries the sync history in descending event time order
-         * @param contentResolver the ContentResolver to use for the query
-         * @return the cursor on the History table
-         */
-        public static Cursor query(ContentResolver contentResolver) {
-            return contentResolver.query(CONTENT_URI, null, null, null, EVENT_TIME + " desc");
-        }
-
-        public static boolean hasNewerSyncFinished(ContentResolver contentResolver,
-                String account, String authority, long when) {
-            Cursor c = contentResolver.query(CONTENT_URI, new String[]{_ID},
-                    FINISHED_SINCE_WHERE_CLAUSE,
-                    new String[]{Long.toString(when), account, authority}, null);
-            try {
-              return c.getCount() > 0;
-            } finally {
-                c.close();
-            }
-        }
-    }
-
-    /**
-     * Provides constants and utility methods to access and use the authority history
-     * table, which contains information about syncs aggregated by account and authority.
-     * All the HistoryColumns except for EVENT are present, plus the AuthorityHistoryColumns.
-     */
-    public static class Status extends History implements StatusColumns {
-
-        /**
-         * The content url for this table.
-         */
-        public static final Uri CONTENT_URI = Uri.parse("content://sync/status");
-
-        // utility class
-        private Status() {}
-
-        /**
-         * returns a cursor that queries the authority sync history in descending event order of
-         * ACCOUNT, AUTHORITY
-         * @param contentResolver the ContentResolver to use for the query
-         * @return the cursor on the AuthorityHistory table
-         */
-        public static Cursor query(ContentResolver contentResolver) {
-            return contentResolver.query(CONTENT_URI, null, null, null, ACCOUNT + ", " + AUTHORITY);
-        }
-
-        public static class QueryMap extends ContentQueryMap {
-            public QueryMap(ContentResolver contentResolver,
-                    boolean keepUpdated,
-                    Handler handlerForUpdateNotifications) {
-                super(contentResolver.query(CONTENT_URI, null, null, null, null),
-                        _ID, keepUpdated, handlerForUpdateNotifications);
-            }
-
-            public ContentValues get(String account, String authority) {
-                Map<String, ContentValues> rows = getRows();
-                for (ContentValues values : rows.values()) {
-                    if (values.getAsString(ACCOUNT).equals(account)
-                            && values.getAsString(AUTHORITY).equals(authority)) {
-                        return values;
-                    }
-                }
-                return null;
-            }
-        }
-    }
-
-    /**
-     * Provides constants and utility methods to access and use the pending syncs table
-     */
-    public static final class Pending implements BaseColumns,
-                                                 StatsColumns {
-
-        /**
-         * The content url for this table.
-         */
-        public static final Uri CONTENT_URI = Uri.parse("content://sync/pending");
-
-        // utility class
-        private Pending() {}
-
-        public static class QueryMap extends ContentQueryMap {
-            public QueryMap(ContentResolver contentResolver, boolean keepUpdated,
-                    Handler handlerForUpdateNotifications) {
-                super(contentResolver.query(CONTENT_URI, null, null, null, null), _ID, keepUpdated,
-                        handlerForUpdateNotifications);
-            }
-
-            public boolean isPending(String account, String authority) {
-                Map<String, ContentValues> rows = getRows();
-                for (ContentValues values : rows.values()) {
-                    if (values.getAsString(ACCOUNT).equals(account)
-                            && values.getAsString(AUTHORITY).equals(authority)) {
-                        return true;
-                    }
-                }
-                return false;
-            }
-        }
-    }
-
-    /**
-     * Columns from the history table.
-     */
-    public interface ActiveColumns {
-        /**
-         * The wallclock time of when the active sync started.
-         * <P>Type: INTEGER</P>
-         */
-        public static final String START_TIME = "startTime";
-    }
-
-    /**
-     * Provides constants and utility methods to access and use the pending syncs table
-     */
-    public static final class Active implements BaseColumns,
-                                                StatsColumns,
-                                                ActiveColumns {
-
-        /**
-         * The content url for this table.
-         */
-        public static final Uri CONTENT_URI = Uri.parse("content://sync/active");
-
-        // utility class
-        private Active() {}
-
-        public static class QueryMap extends ContentQueryMap {
-            public QueryMap(ContentResolver contentResolver, boolean keepUpdated,
-                    Handler handlerForUpdateNotifications) {
-                super(contentResolver.query(CONTENT_URI, null, null, null, null), _ID, keepUpdated,
-                        handlerForUpdateNotifications);
-            }
-
-            public ContentValues getActiveSyncInfo() {
-                Map<String, ContentValues> rows = getRows();
-                for (ContentValues values : rows.values()) {
-                    return values;
-                }
-                return null;
-            }
-
-            public String getSyncingAccount() {
-                ContentValues values = getActiveSyncInfo();
-                return (values == null) ? null : values.getAsString(ACCOUNT);
-            }
-
-            public String getSyncingAuthority() {
-                ContentValues values = getActiveSyncInfo();
-                return (values == null) ? null : values.getAsString(AUTHORITY);
-            }
-
-            public long getSyncStartTime() {
-                ContentValues values = getActiveSyncInfo();
-                return (values == null) ? -1 : values.getAsLong(START_TIME);
-            }
-        }
-    }
-
-    /**
-     * Columns in the settings table, which holds key/value pairs of settings.
-     */
-    public interface SettingsColumns {
-        /**
-         * The key of the setting
-         * <P>Type: TEXT</P>
-         */
-        public static final String KEY = "name";
-
-        /**
-         * The value of the settings
-         * <P>Type: TEXT</P>
-         */
-        public static final String VALUE = "value";
-    }
-
-    /**
-     * Provides constants and utility methods to access and use the settings
-     * table.
-     */
-    public static final class Settings implements BaseColumns, SettingsColumns {
-        /**
-         * The Uri of the settings table. This table behaves a little differently than
-         * normal tables. Updates are not allowed, only inserts, and inserts cause a replace
-         * to be performed, which first deletes the row if it is already present.
-         */
-        public static final Uri CONTENT_URI = Uri.parse("content://sync/settings");
-
-        /** controls whether or not the device listens for sync tickles */
-        public static final String SETTING_LISTEN_FOR_TICKLES = "listen_for_tickles";
-
-        /** controls whether or not the individual provider is synced when tickles are received */
-        public static final String SETTING_SYNC_PROVIDER_PREFIX = "sync_provider_";
-
-        /** query column project */
-        private static final String[] PROJECTION = { KEY, VALUE };
-
-        /**
-         * Convenience function for updating a single settings value as a
-         * boolean. This will either create a new entry in the table if the
-         * given name does not exist, or modify the value of the existing row
-         * with that name.  Note that internally setting values are always
-         * stored as strings, so this function converts the given value to a
-         * string before storing it.
-         *
-         * @param contentResolver the ContentResolver to use to access the settings table
-         * @param name The name of the setting to modify.
-         * @param val The new value for the setting.
-         */
-        static private void putBoolean(ContentResolver contentResolver, String name, boolean val) {
-            ContentValues values = new ContentValues();
-            values.put(KEY, name);
-            values.put(VALUE, Boolean.toString(val));
-            // this insert is translated into an update by the underlying Sync provider
-            contentResolver.insert(CONTENT_URI, values);
-        }
-
-        /**
-         * Convenience function for getting a setting value as a boolean without using the
-         * QueryMap for light-weight setting querying.
-         * @param contentResolver The ContentResolver for querying the setting.
-         * @param name The name of the setting to query
-         * @param def The default value for the setting.
-         * @return The value of the setting.
-         */
-        static public boolean getBoolean(ContentResolver contentResolver,
-                String name, boolean def) {
-            Cursor cursor = contentResolver.query(
-                    CONTENT_URI,
-                    PROJECTION,
-                    KEY + "=?",
-                    new String[] { name },
-                    null);
-            try {
-                if (cursor != null && cursor.moveToFirst()) {
-                    return Boolean.parseBoolean(cursor.getString(1));
-                }
-            } finally {
-                if (cursor != null) cursor.close();
-            }
-            return def;
-        }
-
-        /**
-         * A convenience method to set whether or not the provider is synced when
-         * it receives a network tickle.
-         *
-         * @param contentResolver the ContentResolver to use to access the settings table
-         * @param providerName the provider whose behavior is being controlled
-         * @param sync true if the provider should be synced when tickles are received for it
-         */
-        static public void setSyncProviderAutomatically(ContentResolver contentResolver,
-                String providerName, boolean sync) {
-            putBoolean(contentResolver, SETTING_SYNC_PROVIDER_PREFIX + providerName, sync);
-        }
-
-        /**
-         * A convenience method to set whether or not the device should listen to tickles.
-         *
-         * @param contentResolver the ContentResolver to use to access the settings table
-         * @param flag true if it should listen.
-         */
-        static public void setListenForNetworkTickles(ContentResolver contentResolver,
-                boolean flag) {
-            putBoolean(contentResolver, SETTING_LISTEN_FOR_TICKLES, flag);
-        }
-
-        public static class QueryMap extends ContentQueryMap {
-            private ContentResolver mContentResolver;
-
-            public QueryMap(ContentResolver contentResolver, boolean keepUpdated,
-                    Handler handlerForUpdateNotifications) {
-                super(contentResolver.query(CONTENT_URI, null, null, null, null), KEY, keepUpdated,
-                        handlerForUpdateNotifications);
-                mContentResolver = contentResolver;
-            }
-
-            /**
-             * Check if the provider should be synced when a network tickle is received
-             * @param providerName the provider whose setting we are querying
-             * @return true of the provider should be synced when a network tickle is received
-             */
-            public boolean getSyncProviderAutomatically(String providerName) {
-                return getBoolean(SETTING_SYNC_PROVIDER_PREFIX + providerName, true);
-            }
-
-            /**
-             * Set whether or not the provider is synced when it receives a network tickle.
-             *
-             * @param providerName the provider whose behavior is being controlled
-             * @param sync true if the provider should be synced when tickles are received for it
-             */
-            public void setSyncProviderAutomatically(String providerName, boolean sync) {
-                Settings.setSyncProviderAutomatically(mContentResolver, providerName, sync);
-            }
-
-            /**
-             * Set whether or not the device should listen for tickles.
-             *
-             * @param flag true if it should listen.
-             */
-            public void setListenForNetworkTickles(boolean flag) {
-                Settings.setListenForNetworkTickles(mContentResolver, flag);
-            }
-
-            /**
-             * Check if the device should listen to tickles.
-
-             * @return true if it should
-             */
-            public boolean getListenForNetworkTickles() {
-                return getBoolean(SETTING_LISTEN_FOR_TICKLES, true);
-            }
-
-            /**
-             * Convenience function for retrieving a single settings value
-             * as a boolean.
-             *
-             * @param name The name of the setting to retrieve.
-             * @param def Value to return if the setting is not defined.
-             * @return The setting's current value, or 'def' if it is not defined.
-             */
-            private boolean getBoolean(String name, boolean def) {
-                ContentValues values = getValues(name);
-                return values != null ? values.getAsBoolean(VALUE) : def;
-            }
-        }
-    }
-}
diff --git a/core/java/com/android/internal/os/AtomicFile.java b/core/java/com/android/internal/os/AtomicFile.java
new file mode 100644
index 0000000..ca0345f
--- /dev/null
+++ b/core/java/com/android/internal/os/AtomicFile.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.os.FileUtils;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Helper class for performing atomic operations on a file, by creating a
+ * backup file until a write has successfully completed.
+ */
+public class AtomicFile {
+    private final File mBaseName;
+    private final File mBackupName;
+    
+    public AtomicFile(File baseName) {
+        mBaseName = baseName;
+        mBackupName = new File(baseName.getPath() + ".bak");
+    }
+    
+    public File getBaseFile() {
+        return mBaseName;
+    }
+    
+    public FileOutputStream startWrite() throws IOException {
+        // Rename the current file so it may be used as a backup during the next read
+        if (mBaseName.exists()) {
+            if (!mBaseName.renameTo(mBackupName)) {
+                mBackupName.delete();
+                if (!mBaseName.renameTo(mBackupName)) {
+                    Log.w("AtomicFile", "Couldn't rename file " + mBaseName
+                            + " to backup file " + mBackupName);
+                }
+            }
+        }
+        FileOutputStream str = null;
+        try {
+            str = new FileOutputStream(mBaseName);
+        } catch (FileNotFoundException e) {
+            File parent = mBaseName.getParentFile();
+            if (!parent.mkdir()) {
+                throw new IOException("Couldn't create directory " + mBaseName);
+            }
+            FileUtils.setPermissions(
+                parent.getPath(),
+                FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+                -1, -1);
+            try {
+                str = new FileOutputStream(mBaseName);
+            } catch (FileNotFoundException e2) {
+                throw new IOException("Couldn't create " + mBaseName);
+            }
+        }
+        return str;
+    }
+    
+    public void finishWrite(FileOutputStream str) {
+        if (str != null) {
+            try {
+                str.close();
+                mBackupName.delete();
+            } catch (IOException e) {
+                Log.w("AtomicFile", "finishWrite: Got exception:", e);
+            }
+        }
+    }
+    
+    public void failWrite(FileOutputStream str) {
+        if (str != null) {
+            try {
+                str.close();
+                mBaseName.delete();
+                mBackupName.renameTo(mBaseName);
+            } catch (IOException e) {
+                Log.w("AtomicFile", "failWrite: Got exception:", e);
+            }
+        }
+    }
+    
+    public FileOutputStream openAppend() throws IOException {
+        try {
+            return new FileOutputStream(mBaseName, true);
+        } catch (FileNotFoundException e) {
+            throw new IOException("Couldn't append " + mBaseName);
+        }
+    }
+    
+    public void truncate() throws IOException {
+        try {
+            FileOutputStream fos = new FileOutputStream(mBaseName);
+            fos.close();
+        } catch (FileNotFoundException e) {
+            throw new IOException("Couldn't append " + mBaseName);
+        } catch (IOException e) {
+        }
+    }
+    
+    public FileInputStream openRead() throws FileNotFoundException {
+        if (mBackupName.exists()) {
+            mBaseName.delete();
+            mBackupName.renameTo(mBaseName);
+        }
+        return new FileInputStream(mBaseName);
+    }
+    
+    public byte[] readFully() throws IOException {
+        FileInputStream stream = openRead();
+        try {
+            int pos = 0;
+            int avail = stream.available();
+            byte[] data = new byte[avail];
+            while (true) {
+                int amt = stream.read(data, pos, data.length-pos);
+                //Log.i("foo", "Read " + amt + " bytes at " + pos
+                //        + " of avail " + data.length);
+                if (amt <= 0) {
+                    //Log.i("foo", "**** FINISHED READING: pos=" + pos
+                    //        + " len=" + data.length);
+                    return data;
+                }
+                pos += amt;
+                avail = stream.available();
+                if (avail > data.length-pos) {
+                    byte[] newData = new byte[pos+avail];
+                    System.arraycopy(data, 0, newData, 0, pos);
+                    data = newData;
+                }
+            }
+        } finally {
+            stream.close();
+        }
+    }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index aa8fd74..7ebc896 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1012,9 +1012,6 @@
                 android:excludeFromRecents="true">
         </activity>
 
-        <provider android:name=".content.SyncProvider"
-                android:authorities="sync" android:multiprocess="false" />
-
         <service android:name="com.android.server.LoadAverageService"
             android:exported="true" />
 
diff --git a/packages/SubscribedFeedsProvider/src/com/android/providers/subscribedfeeds/SubscribedFeedsIntentService.java b/packages/SubscribedFeedsProvider/src/com/android/providers/subscribedfeeds/SubscribedFeedsIntentService.java
index df599c7..8b3bedf 100644
--- a/packages/SubscribedFeedsProvider/src/com/android/providers/subscribedfeeds/SubscribedFeedsIntentService.java
+++ b/packages/SubscribedFeedsProvider/src/com/android/providers/subscribedfeeds/SubscribedFeedsIntentService.java
@@ -9,7 +9,6 @@
 import android.util.Config;
 import android.util.EventLog;
 import android.app.IntentService;
-import android.provider.Sync;
 import android.provider.SubscribedFeeds;
 import android.provider.SyncConstValue;
 import android.database.Cursor;
@@ -17,7 +16,7 @@
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.os.Bundle;
-import android.os.Debug;
+import android.os.RemoteException;
 import android.text.TextUtils;
 import android.net.Uri;
 
@@ -105,10 +104,6 @@
 
     private void handleTickle(Context context, String account, String feed) {
         Cursor c = null;
-        Sync.Settings.QueryMap syncSettings =
-                new Sync.Settings.QueryMap(context.getContentResolver(),
-                        false /* don't keep updated */,
-                        null /* not needed since keep updated is false */);
         final String where = SubscribedFeeds.Feeds._SYNC_ACCOUNT + "= ? "
                 + "and " + SubscribedFeeds.Feeds.FEED + "= ?";
         try {
@@ -124,9 +119,14 @@
                 String authority = c.getString(c.getColumnIndexOrThrow(
                         SubscribedFeeds.Feeds.AUTHORITY));
                 EventLog.writeEvent(LOG_TICKLE, authority);
-                if (!syncSettings.getSyncProviderAutomatically(authority)) {
-                    Log.d(TAG, "supressing tickle since provider " + authority
-                            + " is configured to not sync automatically");
+                try {
+                    if (!ContentResolver.getContentService()
+                            .getSyncProviderAutomatically(authority)) {
+                        Log.d(TAG, "supressing tickle since provider " + authority
+                                + " is configured to not sync automatically");
+                        continue;
+                    }
+                } catch (RemoteException e) {
                     continue;
                 }
                 Uri uri = Uri.parse("content://" + authority);
@@ -137,7 +137,6 @@
             }
         } finally {
             if (c != null) c.deactivate();
-            syncSettings.close();
         }
     }
 
diff --git a/preloaded-classes b/preloaded-classes
index 7d719e9..0520e41 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -76,8 +76,6 @@
 android.content.ContentQueryMap$1
 android.content.ContentResolver
 android.content.ContentResolver$CursorWrapperInner
-android.content.ContentServiceNative
-android.content.ContentServiceProxy
 android.content.ContentValues
 android.content.Context
 android.content.ContextWrapper
@@ -85,6 +83,8 @@
 android.content.DialogInterface$OnCancelListener
 android.content.DialogInterface$OnDismissListener
 android.content.IContentProvider
+android.content.IContentService
+android.content.IContentService$Stub
 android.content.Intent
 android.content.Intent$1
 android.content.IntentFilter
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 760988d..493bd09 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -35,7 +35,6 @@
 import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.provider.Settings;
-import android.provider.Sync;
 import android.util.EventLog;
 import android.util.Log;
 
diff --git a/test-runner/android/test/SyncBaseInstrumentation.java b/test-runner/android/test/SyncBaseInstrumentation.java
index c1d2507..772d75c 100644
--- a/test-runner/android/test/SyncBaseInstrumentation.java
+++ b/test-runner/android/test/SyncBaseInstrumentation.java
@@ -18,12 +18,10 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.ContentValues;
 import android.os.Bundle;
+import android.os.RemoteException;
 import android.os.SystemClock;
-import android.provider.Sync;
 import android.net.Uri;
-import java.util.Map;
 
 /**
  * If you would like to test sync a single provider with an
@@ -75,11 +73,11 @@
     }
 
     protected void cancelSyncsandDisableAutoSync() {
-        Sync.Settings.QueryMap mSyncSettings =
-                new Sync.Settings.QueryMap(mContentResolver, true, null);
-        mSyncSettings.setListenForNetworkTickles(false);
+        try {
+            ContentResolver.getContentService().setListenForNetworkTickles(false);
+        } catch (RemoteException e) {
+        }
         mContentResolver.cancelSync(null);
-        mSyncSettings.close();
     }
 
     /**
@@ -88,34 +86,11 @@
      * @return
      */
     private boolean isSyncActive(String account, String authority) {
-        Sync.Pending.QueryMap pendingQueryMap = null;
-        Sync.Active.QueryMap activeQueryMap = null;
         try {
-            pendingQueryMap = new Sync.Pending.QueryMap(mContentResolver, false, null);
-            activeQueryMap = new Sync.Active.QueryMap(mContentResolver, false, null);
-
-            if (pendingQueryMap.isPending(account, authority)) {
-                return true;
-            }
-            if (isActiveInActiveQueryMap(activeQueryMap, account, authority)) {
-                return true;
-            }
+            return ContentResolver.getContentService().isSyncActive(account,
+                    authority);
+        } catch (RemoteException e) {
             return false;
-        } finally {
-            activeQueryMap.close();
-            pendingQueryMap.close();
         }
     }
-
-    private boolean isActiveInActiveQueryMap(Sync.Active.QueryMap activemap, String account,
-                                             String authority) {
-        Map<String, ContentValues> rows = activemap.getRows();
-        for (ContentValues values : rows.values()) {
-            if (values.getAsString("account").equals(account)
-                    && values.getAsString("authority").equals(authority)) {
-                return true;
-            }
-        }
-        return false;
-    }
 }
diff --git a/tests/CoreTests/android/content/SyncStorageEngineTest.java b/tests/CoreTests/android/content/SyncStorageEngineTest.java
index 36805b1..dee6e38 100644
--- a/tests/CoreTests/android/content/SyncStorageEngineTest.java
+++ b/tests/CoreTests/android/content/SyncStorageEngineTest.java
@@ -20,7 +20,6 @@
 import android.test.RenamingDelegatingContext;
 import android.test.mock.MockContext;
 import android.test.mock.MockContentResolver;
-import android.provider.Sync;
 
 public class SyncStorageEngineTest extends AndroidTestCase {
 
@@ -39,7 +38,7 @@
 
         long time0 = 1000;
         long historyId = engine.insertStartSyncEvent(
-                account, authority, time0, Sync.History.SOURCE_LOCAL);
+                account, authority, time0, SyncStorageEngine.SOURCE_LOCAL);
         long time1 = time0 + SyncStorageEngine.MILLIS_IN_4WEEKS * 2;
         engine.stopSyncEvent(historyId, time1 - time0, "yay", 0, 0);
     }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContentResolver.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContentResolver.java
index 727d6f2..cfab90a 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContentResolver.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContentResolver.java
@@ -17,7 +17,6 @@
 package com.android.layoutlib.bridge;
 
 import android.content.ContentResolver;
-import android.content.ContentServiceNative;
 import android.content.Context;
 import android.content.IContentProvider;
 import android.database.ContentObserver;
@@ -51,9 +50,6 @@
     
     /**
      * Stub for the layoutlib bridge content resolver.
-     * <p/>
-     * The super implementation accesses the {@link ContentServiceNative#getDefault()}
-     * which returns null and would make the call crash. Instead we do nothing.
      */
     @Override
     public void registerContentObserver(Uri uri, boolean notifyForDescendents,
@@ -63,9 +59,6 @@
     
     /**
      * Stub for the layoutlib bridge content resolver.
-     * <p/>
-     * The super implementation accesses the {@link ContentServiceNative#getDefault()}
-     * which returns null and would make the call crash. Instead we do nothing.
      */
     @Override
     public void unregisterContentObserver(ContentObserver observer) {
@@ -74,9 +67,6 @@
     
     /**
      * Stub for the layoutlib bridge content resolver.
-     * <p/>
-     * The super implementation accesses the {@link ContentServiceNative#getDefault()}
-     * which returns null and would make the call crash. Instead we do nothing.
      */
     @Override
     public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
@@ -85,9 +75,6 @@
     
     /**
      * Stub for the layoutlib bridge content resolver.
-     * <p/>
-     * The super implementation accesses the {@link ContentServiceNative#getDefault()}
-     * which returns null and would make the call crash. Instead we do nothing.
      */
     @Override
     public void startSync(Uri uri, Bundle extras) {
@@ -96,9 +83,6 @@
     
     /**
      * Stub for the layoutlib bridge content resolver.
-     * <p/>
-     * The super implementation accesses the {@link ContentServiceNative#getDefault()}
-     * which returns null and would make the call crash. Instead we do nothing.
      */
     @Override
     public void cancelSync(Uri uri) {