Merge "fix [2189862] Race condition in eglIntialize and eglDestroy"
diff --git a/Android.mk b/Android.mk
index 682f286..ab1e7ea 100644
--- a/Android.mk
+++ b/Android.mk
@@ -222,6 +222,7 @@
 	frameworks/base/core/java/android/content/ComponentName.aidl \
 	frameworks/base/core/java/android/content/Intent.aidl \
 	frameworks/base/core/java/android/content/IntentSender.aidl \
+	frameworks/base/core/java/android/content/PeriodicSync.aidl \
 	frameworks/base/core/java/android/content/SyncStats.aidl \
 	frameworks/base/core/java/android/content/res/Configuration.aidl \
 	frameworks/base/core/java/android/appwidget/AppWidgetProviderInfo.aidl \
diff --git a/api/current.xml b/api/current.xml
index 52648852..16ed39b 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -1035,6 +1035,17 @@
  visibility="public"
 >
 </field>
+<field name="SET_TIME"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.permission.SET_TIME&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="SET_TIME_ZONE"
  type="java.lang.String"
  transient="false"
@@ -18808,6 +18819,19 @@
 <parameter name="operation" type="android.app.PendingIntent">
 </parameter>
 </method>
+<method name="setTime"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="millis" type="long">
+</parameter>
+</method>
 <method name="setTimeZone"
  return="void"
  abstract="false"
@@ -31083,6 +31107,25 @@
 <parameter name="name" type="java.lang.String">
 </parameter>
 </method>
+<method name="addPeriodicSync"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="account" type="android.accounts.Account">
+</parameter>
+<parameter name="authority" type="java.lang.String">
+</parameter>
+<parameter name="extras" type="android.os.Bundle">
+</parameter>
+<parameter name="pollFrequency" type="long">
+</parameter>
+</method>
 <method name="addStatusChangeListener"
  return="java.lang.Object"
  abstract="false"
@@ -31203,6 +31246,21 @@
  visibility="public"
 >
 </method>
+<method name="getPeriodicSyncs"
+ return="java.util.List&lt;android.content.PeriodicSync&gt;"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="account" type="android.accounts.Account">
+</parameter>
+<parameter name="authority" type="java.lang.String">
+</parameter>
+</method>
 <method name="getSyncAdapterTypes"
  return="android.content.SyncAdapterType[]"
  abstract="false"
@@ -31438,6 +31496,23 @@
 <parameter name="observer" type="android.database.ContentObserver">
 </parameter>
 </method>
+<method name="removePeriodicSync"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="account" type="android.accounts.Account">
+</parameter>
+<parameter name="authority" type="java.lang.String">
+</parameter>
+<parameter name="extras" type="android.os.Bundle">
+</parameter>
+</method>
 <method name="removeStatusChangeListener"
  return="void"
  abstract="false"
@@ -39721,6 +39796,109 @@
 >
 </method>
 </class>
+<class name="PeriodicSync"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.os.Parcelable">
+</implements>
+<constructor name="PeriodicSync"
+ type="android.content.PeriodicSync"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="account" type="android.accounts.Account">
+</parameter>
+<parameter name="authority" type="java.lang.String">
+</parameter>
+<parameter name="extras" type="android.os.Bundle">
+</parameter>
+<parameter name="period" type="long">
+</parameter>
+</constructor>
+<method name="describeContents"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="writeToParcel"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="dest" type="android.os.Parcel">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+<field name="CREATOR"
+ type="android.os.Parcelable.Creator"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="account"
+ type="android.accounts.Account"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="authority"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="extras"
+ type="android.os.Bundle"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="period"
+ type="long"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
 <class name="ReceiverCallNotAllowedException"
  extends="android.util.AndroidRuntimeException"
  abstract="false"
@@ -192449,6 +192627,49 @@
 <parameter name="mode" type="int">
 </parameter>
 </method>
+<method name="smoothScrollBy"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="distance" type="int">
+</parameter>
+<parameter name="duration" type="int">
+</parameter>
+</method>
+<method name="smoothScrollToPosition"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="position" type="int">
+</parameter>
+</method>
+<method name="smoothScrollToPosition"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="position" type="int">
+</parameter>
+<parameter name="boundPosition" type="int">
+</parameter>
+</method>
 <method name="verifyDrawable"
  return="boolean"
  abstract="false"
diff --git a/cmds/stagefright/record.cpp b/cmds/stagefright/record.cpp
index 2ec0b70..845c854 100644
--- a/cmds/stagefright/record.cpp
+++ b/cmds/stagefright/record.cpp
@@ -106,6 +106,9 @@
 
     sp<MediaExtractor> extractor =
         MediaExtractor::Create(new FileSource(filename));
+    if (extractor == NULL) {
+        return NULL;
+    }
 
     size_t num_tracks = extractor->countTracks();
 
diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp
index e65cdf1..f7cb227 100644
--- a/cmds/stagefright/stagefright.cpp
+++ b/cmds/stagefright/stagefright.cpp
@@ -431,6 +431,10 @@
             mediaSource = new JPEGSource(dataSource);
         } else {
             sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);
+            if (extractor == NULL) {
+                fprintf(stderr, "could not create data source\n");
+                return -1;
+            }
 
             size_t numTracks = extractor->countTracks();
 
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 414d963..19e741a 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -271,7 +271,7 @@
     }
 
     /**
-     * Add an account to the AccountManager's set of known accounts. 
+     * Add an account to the AccountManager's set of known accounts.
      * <p>
      * Requires that the caller has permission
      * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
@@ -560,9 +560,13 @@
      * user to enter credentials. If it is able to retrieve the authtoken it will be returned
      * in the result.
      * <p>
-     * If the authenticator needs to prompt the user for credentials it will return an intent for
+     * If the authenticator needs to prompt the user for credentials, rather than returning the
+     * authtoken it will instead return an intent for
      * an activity that will do the prompting. If an intent is returned and notifyAuthFailure
-     * is true then a notification will be created that launches this intent.
+     * is true then a notification will be created that launches this intent. This intent can be
+     * invoked by the caller directly to start the activity that prompts the user for the
+     * updated credentials. Otherwise this activity will not be run until the user activates
+     * the notification.
      * <p>
      * This call returns immediately but runs asynchronously and the result is accessed via the
      * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
@@ -653,7 +657,7 @@
                 if (accountType == null) {
                     Log.e(TAG, "the account must not be null");
                     // to unblock caller waiting on Future.get()
-                    set(new Bundle()); 
+                    set(new Bundle());
                     return;
                 }
                 mService.addAcount(mResponse, accountType, authTokenType,
@@ -1372,7 +1376,7 @@
                 IntentFilter intentFilter = new IntentFilter();
                 intentFilter.addAction(LOGIN_ACCOUNTS_CHANGED_ACTION);
                 // To recover from disk-full.
-                intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); 
+                intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
                 mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter);
             }
         }
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
index 53c7935..9082003 100644
--- a/core/java/android/app/AlarmManager.java
+++ b/core/java/android/app/AlarmManager.java
@@ -277,7 +277,26 @@
         } catch (RemoteException ex) {
         }
     }
-    
+
+    /**
+     * Set the system wall clock time.
+     * Requires the permission android.permission.SET_TIME.
+     *
+     * @param millis time in milliseconds since the Epoch
+     */
+    public void setTime(long millis) {
+        try {
+            mService.setTime(millis);
+        } catch (RemoteException ex) {
+        }
+    }
+
+    /**
+     * Set the system default time zone.
+     * Requires the permission android.permission.SET_TIME_ZONE.
+     *
+     * @param timeZone in the format understood by {@link java.util.TimeZone}
+     */
     public void setTimeZone(String timeZone) {
         try {
             mService.setTimeZone(timeZone);
diff --git a/core/java/android/app/IAlarmManager.aidl b/core/java/android/app/IAlarmManager.aidl
index cb42236..edb40ed 100755
--- a/core/java/android/app/IAlarmManager.aidl
+++ b/core/java/android/app/IAlarmManager.aidl
@@ -27,6 +27,7 @@
     void set(int type, long triggerAtTime, in PendingIntent operation);
     void setRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation);
     void setInexactRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation);
+    void setTime(long millis);
     void setTimeZone(String zone);
     void remove(in PendingIntent operation);
 }
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index eb2d7b1..b5587ed 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -42,6 +42,7 @@
 import java.io.OutputStream;
 import java.util.List;
 import java.util.ArrayList;
+import java.util.Collection;
 
 
 /**
@@ -966,6 +967,65 @@
     }
 
     /**
+     * Specifies that a sync should be requested with the specified the account, authority,
+     * and extras at the given frequency. If there is already another periodic sync scheduled
+     * with the account, authority and extras then a new periodic sync won't be added, instead
+     * the frequency of the previous one will be updated.
+     * <p>
+     * These periodic syncs honor the "syncAutomatically" and "masterSyncAutomatically" settings.
+     * Although these sync are scheduled at the specified frequency, it may take longer for it to
+     * actually be started if other syncs are ahead of it in the sync operation queue. This means
+     * that the actual start time may drift.
+     *
+     * @param account the account to specify in the sync
+     * @param authority the provider to specify in the sync request
+     * @param extras extra parameters to go along with the sync request
+     * @param pollFrequency how frequently the sync should be performed, in seconds.
+     */
+    public static void addPeriodicSync(Account account, String authority, Bundle extras,
+            long pollFrequency) {
+        validateSyncExtrasBundle(extras);
+        try {
+            getContentService().addPeriodicSync(account, authority, extras, pollFrequency);
+        } catch (RemoteException e) {
+            // exception ignored; if this is thrown then it means the runtime is in the midst of
+            // being restarted
+        }
+    }
+
+    /**
+     * Remove a periodic sync. Has no affect if account, authority and extras don't match
+     * an existing periodic sync.
+     *
+     * @param account the account of the periodic sync to remove
+     * @param authority the provider of the periodic sync to remove
+     * @param extras the extras of the periodic sync to remove
+     */
+    public static void removePeriodicSync(Account account, String authority, Bundle extras) {
+        validateSyncExtrasBundle(extras);
+        try {
+            getContentService().removePeriodicSync(account, authority, extras);
+        } catch (RemoteException e) {
+            throw new RuntimeException("the ContentService should always be reachable", e);
+        }
+    }
+
+    /**
+     * Get the list of information about the periodic syncs for the given account and authority.
+     *
+     * @param account the account whose periodic syncs we are querying
+     * @param authority the provider whose periodic syncs we are querying
+     * @return a list of PeriodicSync objects. This list may be empty but will never be null.
+     */
+    public static List<PeriodicSync> getPeriodicSyncs(Account account, String authority) {
+        try {
+            return getContentService().getPeriodicSyncs(account, authority);
+        } catch (RemoteException e) {
+            throw new RuntimeException("the ContentService should always be reachable", e);
+        }
+    }
+
+    /**
      * Check if this account/provider is syncable.
      * @return >0 if it is syncable, 0 if not, and <0 if the state isn't known yet.
      */
diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java
index 974a667..e0dfab5 100644
--- a/core/java/android/content/ContentService.java
+++ b/core/java/android/content/ContentService.java
@@ -32,6 +32,8 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
 
 /**
  * {@hide}
@@ -273,6 +275,42 @@
         }
     }
 
+    public void addPeriodicSync(Account account, String authority, Bundle extras,
+            long pollFrequency) {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+                "no permission to write the sync settings");
+        long identityToken = clearCallingIdentity();
+        try {
+            getSyncManager().getSyncStorageEngine().addPeriodicSync(
+                    account, authority, extras, pollFrequency);
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    public void removePeriodicSync(Account account, String authority, Bundle extras) {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+                "no permission to write the sync settings");
+        long identityToken = clearCallingIdentity();
+        try {
+            getSyncManager().getSyncStorageEngine().removePeriodicSync(account, authority, extras);
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
+                "no permission to read the sync settings");
+        long identityToken = clearCallingIdentity();
+        try {
+            return getSyncManager().getSyncStorageEngine().getPeriodicSyncs(
+                    account, providerName);
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
     public int getIsSyncable(Account account, String providerName) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
                 "no permission to read the sync settings");
diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl
index b0f14c1..2d906ed 100644
--- a/core/java/android/content/IContentService.aidl
+++ b/core/java/android/content/IContentService.aidl
@@ -21,6 +21,7 @@
 import android.content.ISyncStatusObserver;
 import android.content.SyncAdapterType;
 import android.content.SyncStatusInfo;
+import android.content.PeriodicSync;
 import android.net.Uri;
 import android.os.Bundle;
 import android.database.IContentObserver;
@@ -38,11 +39,11 @@
 
     void requestSync(in Account account, String authority, in Bundle extras);
     void cancelSync(in Account account, String authority);
-    
+
     /**
      * 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
+     * @return true if the provider should be synced when a network tickle is received
      */
     boolean getSyncAutomatically(in Account account, String providerName);
 
@@ -55,6 +56,33 @@
     void setSyncAutomatically(in Account account, String providerName, boolean sync);
 
     /**
+     * Get the frequency of the periodic poll, if any.
+     * @param providerName the provider whose setting we are querying
+     * @return the frequency of the periodic sync in seconds. If 0 then no periodic syncs
+     * will take place.
+     */
+    List<PeriodicSync> getPeriodicSyncs(in Account account, String providerName);
+
+    /**
+     * Set whether or not the provider is to be synced on a periodic basis.
+     *
+     * @param providerName the provider whose behavior is being controlled
+     * @param pollFrequency the period that a sync should be performed, in seconds. If this is
+     * zero or less then no periodic syncs will be performed.
+     */
+    void addPeriodicSync(in Account account, String providerName, in Bundle extras,
+      long pollFrequency);
+
+    /**
+     * Set whether or not the provider is to be synced on a periodic basis.
+     *
+     * @param providerName the provider whose behavior is being controlled
+     * @param pollFrequency the period that a sync should be performed, in seconds. If this is
+     * zero or less then no periodic syncs will be performed.
+     */
+    void removePeriodicSync(in Account account, String providerName, in Bundle extras);
+
+    /**
      * Check if this account/provider is syncable.
      * @return >0 if it is syncable, 0 if not, and <0 if the state isn't known yet.
      */
@@ -69,15 +97,15 @@
     void setMasterSyncAutomatically(boolean flag);
 
     boolean getMasterSyncAutomatically();
-    
+
     /**
      * Returns true if there is currently a sync operation for the given
      * account or authority in the pending list, or actively being processed.
      */
     boolean isSyncActive(in Account account, String authority);
-    
+
     ActiveSyncInfo getActiveSync();
-    
+
     /**
      * Returns the types of the SyncAdapters that are registered with the system.
      * @return Returns the types of the SyncAdapters that are registered with the system.
@@ -96,8 +124,8 @@
      * Return true if the pending status is true of any matching authorities.
      */
     boolean isSyncPending(in Account account, String authority);
-    
+
     void addStatusChangeListener(int mask, ISyncStatusObserver callback);
-    
+
     void removeStatusChangeListener(ISyncStatusObserver callback);
 }
diff --git a/core/java/android/content/PeriodicSync.aidl b/core/java/android/content/PeriodicSync.aidl
new file mode 100644
index 0000000..4530591
--- /dev/null
+++ b/core/java/android/content/PeriodicSync.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2010 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 PeriodicSync;
diff --git a/core/java/android/content/PeriodicSync.java b/core/java/android/content/PeriodicSync.java
new file mode 100644
index 0000000..17813ec
--- /dev/null
+++ b/core/java/android/content/PeriodicSync.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2010 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.Parcelable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.accounts.Account;
+
+/**
+ * Value type that contains information about a periodic sync. Is parcelable, making it suitable
+ * for passing in an IPC.
+ */
+public class PeriodicSync implements Parcelable {
+    /** The account to be synced */
+    public final Account account;
+    /** The authority of the sync */
+    public final String authority;
+    /** Any extras that parameters that are to be passed to the sync adapter. */
+    public final Bundle extras;
+    /** How frequently the sync should be scheduled, in seconds. */
+    public final long period;
+
+    /** Creates a new PeriodicSync, copying the Bundle */
+    public PeriodicSync(Account account, String authority, Bundle extras, long period) {
+        this.account = account;
+        this.authority = authority;
+        this.extras = new Bundle(extras);
+        this.period = period;
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        account.writeToParcel(dest, flags);
+        dest.writeString(authority);
+        dest.writeBundle(extras);
+        dest.writeLong(period);
+    }
+
+    public static final Creator<PeriodicSync> CREATOR = new Creator<PeriodicSync>() {
+        public PeriodicSync createFromParcel(Parcel source) {
+            return new PeriodicSync(Account.CREATOR.createFromParcel(source),
+                    source.readString(), source.readBundle(), source.readLong());
+        }
+
+        public PeriodicSync[] newArray(int size) {
+            return new PeriodicSync[size];
+        }
+    };
+
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+
+        if (!(o instanceof PeriodicSync)) {
+            return false;
+        }
+
+        final PeriodicSync other = (PeriodicSync) o;
+
+        return account.equals(other.account)
+                && authority.equals(other.authority)
+                && period == other.period
+                && SyncStorageEngine.equals(extras, other.extras);
+    }
+}
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 699b61d..619c7d5 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -52,14 +52,7 @@
 import android.util.Log;
 import android.util.Pair;
 
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.File;
 import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -74,12 +67,6 @@
 public class SyncManager implements OnAccountsUpdateListener {
     private static final String TAG = "SyncManager";
 
-    // used during dumping of the Sync history
-    private static final long MILLIS_IN_HOUR = 1000 * 60 * 60;
-    private static final long MILLIS_IN_DAY = MILLIS_IN_HOUR * 24;
-    private static final long MILLIS_IN_WEEK = MILLIS_IN_DAY * 7;
-    private static final long MILLIS_IN_4WEEKS = MILLIS_IN_WEEK * 4;
-
     /** Delay a sync due to local changes this long. In milliseconds */
     private static final long LOCAL_SYNC_DELAY;
 
@@ -157,9 +144,7 @@
     // set if the sync active indicator should be reported
     private boolean mNeedSyncActiveNotification = false;
 
-    private volatile boolean mSyncPollInitialized;
     private final PendingIntent mSyncAlarmIntent;
-    private final PendingIntent mSyncPollAlarmIntent;
     // Synchronized on "this". Instead of using this directly one should instead call
     // its accessor, getConnManager().
     private ConnectivityManager mConnManagerDoNotUseDirectly;
@@ -276,7 +261,6 @@
                     // ignore the rest of the states -- leave our boolean alone.
             }
             if (mDataConnectionIsConnected) {
-                initializeSyncPoll();
                 sendCheckAlarmsMessage();
             }
         }
@@ -291,14 +275,8 @@
     };
 
     private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM";
-    private static final String SYNC_POLL_ALARM = "android.content.syncmanager.SYNC_POLL_ALARM";
     private final SyncHandler mSyncHandler;
 
-    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
-
-    private static final String SYNCMANAGER_PREFS_FILENAME = "/data/system/syncmanager.prefs";
-
     private volatile boolean mBootCompleted = false;
 
     private ConnectivityManager getConnectivityManager() {
@@ -338,9 +316,6 @@
         mSyncAlarmIntent = PendingIntent.getBroadcast(
                 mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0);
 
-        mSyncPollAlarmIntent = PendingIntent.getBroadcast(
-                mContext, 0 /* ignored */, new Intent(SYNC_POLL_ALARM), 0);
-
         IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
         context.registerReceiver(mConnectivityIntentReceiver, intentFilter);
 
@@ -396,49 +371,6 @@
         }
     }
 
-    private synchronized void initializeSyncPoll() {
-        if (mSyncPollInitialized) return;
-        mSyncPollInitialized = true;
-
-        mContext.registerReceiver(new SyncPollAlarmReceiver(), new IntentFilter(SYNC_POLL_ALARM));
-
-        // load the next poll time from shared preferences
-        long absoluteAlarmTime = readSyncPollTime();
-
-        if (Log.isLoggable(TAG, Log.VERBOSE)) {
-            Log.v(TAG, "initializeSyncPoll: absoluteAlarmTime is " + absoluteAlarmTime);
-        }
-
-        // Convert absoluteAlarmTime to elapsed realtime. If this time was in the past then
-        // schedule the poll immediately, if it is too far in the future then cap it at
-        // MAX_SYNC_POLL_DELAY_SECONDS.
-        long absoluteNow = System.currentTimeMillis();
-        long relativeNow = SystemClock.elapsedRealtime();
-        long relativeAlarmTime = relativeNow;
-        if (absoluteAlarmTime > absoluteNow) {
-            long delayInMs = absoluteAlarmTime - absoluteNow;
-            final int maxDelayInMs = MAX_SYNC_POLL_DELAY_SECONDS * 1000;
-            if (delayInMs > maxDelayInMs) {
-                delayInMs = MAX_SYNC_POLL_DELAY_SECONDS * 1000;
-            }
-            relativeAlarmTime += delayInMs;
-        }
-
-        // schedule an alarm for the next poll time
-        scheduleSyncPollAlarm(relativeAlarmTime);
-    }
-
-    private void scheduleSyncPollAlarm(long relativeAlarmTime) {
-        if (Log.isLoggable(TAG, Log.VERBOSE)) {
-            Log.v(TAG, "scheduleSyncPollAlarm: relativeAlarmTime is " + relativeAlarmTime
-                    + ", now is " + SystemClock.elapsedRealtime()
-                    + ", delay is " + (relativeAlarmTime - SystemClock.elapsedRealtime()));
-        }
-        ensureAlarmService();
-        mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, relativeAlarmTime,
-                mSyncPollAlarmIntent);
-    }
-
     /**
      * Return a random value v that satisfies minValue <= v < maxValue. The difference between
      * maxValue and minValue must be less than Integer.MAX_VALUE.
@@ -453,68 +385,6 @@
         return minValue + random.nextInt((int)spread);
     }
 
-    private void handleSyncPollAlarm() {
-        // determine the next poll time
-        long delayMs = jitterize(MIN_SYNC_POLL_DELAY_SECONDS, MAX_SYNC_POLL_DELAY_SECONDS) * 1000;
-        long nextRelativePollTimeMs = SystemClock.elapsedRealtime() + delayMs;
-
-        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "handleSyncPollAlarm: delay " + delayMs);
-
-        // write the absolute time to shared preferences
-        writeSyncPollTime(System.currentTimeMillis() + delayMs);
-
-        // schedule an alarm for the next poll time
-        scheduleSyncPollAlarm(nextRelativePollTimeMs);
-
-        // perform a poll
-        scheduleSync(null /* sync all syncable accounts */, null /* sync all syncable providers */,
-                new Bundle(), 0 /* no delay */, false /* onlyThoseWithUnkownSyncableState */);
-    }
-
-    private void writeSyncPollTime(long when) {
-        File f = new File(SYNCMANAGER_PREFS_FILENAME);
-        DataOutputStream str = null;
-        try {
-            str = new DataOutputStream(new FileOutputStream(f));
-            str.writeLong(when);
-        } catch (FileNotFoundException e) {
-            Log.w(TAG, "error writing to file " + f, e);
-        } catch (IOException e) {
-            Log.w(TAG, "error writing to file " + f, e);
-        } finally {
-            if (str != null) {
-                try {
-                    str.close();
-                } catch (IOException e) {
-                    Log.w(TAG, "error closing file " + f, e);
-                }
-            }
-        }
-    }
-
-    private long readSyncPollTime() {
-        File f = new File(SYNCMANAGER_PREFS_FILENAME);
-
-        DataInputStream str = null;
-        try {
-            str = new DataInputStream(new FileInputStream(f));
-            return str.readLong();
-        } catch (FileNotFoundException e) {
-            writeSyncPollTime(0);
-        } catch (IOException e) {
-            Log.w(TAG, "error reading file " + f, e);
-        } finally {
-            if (str != null) {
-                try {
-                    str.close();
-                } catch (IOException e) {
-                    Log.w(TAG, "error closing file " + f, e);
-                }
-            }
-        }
-        return 0;
-    }
-
     public ActiveSyncContext getActiveSyncContext() {
         return mActiveSyncContext;
     }
@@ -799,12 +669,6 @@
         }
     }
 
-    class SyncPollAlarmReceiver extends BroadcastReceiver {
-        public void onReceive(Context context, Intent intent) {
-            handleSyncPollAlarm();
-        }
-    }
-
     private void clearBackoffSetting(SyncOperation op) {
         mSyncStorageEngine.setBackoff(op.account, op.authority,
                 SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
@@ -923,7 +787,7 @@
         mSyncStorageEngine.setBackoff(account, authority,
                 SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
         synchronized (mSyncQueue) {
-            mSyncQueue.clear(account, authority);
+            mSyncQueue.remove(account, authority);
         }
     }
 
@@ -1084,7 +948,8 @@
             pw.println("none");
         }
         final long now = SystemClock.elapsedRealtime();
-        pw.print("now: "); pw.println(now);
+        pw.print("now: "); pw.print(now);
+        pw.println(" (" + formatTime(System.currentTimeMillis()) + ")");
         pw.print("uptime: "); pw.print(DateUtils.formatElapsedTime(now/1000));
                 pw.println(" (HH:MM:SS)");
         pw.print("time spent syncing: ");
@@ -1102,7 +967,9 @@
             pw.println("no alarm is scheduled (there had better not be any pending syncs)");
         }
 
-        pw.print("active sync: "); pw.println(mActiveSyncContext);
+        final SyncManager.ActiveSyncContext activeSyncContext = mActiveSyncContext;
+
+        pw.print("active sync: "); pw.println(activeSyncContext);
 
         pw.print("notification info: ");
         sb.setLength(0);
@@ -1125,6 +992,11 @@
                     pw.print(authority != null ? authority.account : "<no account>");
                     pw.print(" ");
                     pw.print(authority != null ? authority.authority : "<no account>");
+                    if (activeSyncContext != null) {
+                        pw.print(" ");
+                        pw.print(SyncStorageEngine.SOURCES[
+                                activeSyncContext.mSyncOperation.syncSource]);
+                    }
                     pw.print(", duration is ");
                     pw.println(DateUtils.formatElapsedTime(durationInSeconds));
         } else {
@@ -1152,80 +1024,76 @@
             }
         }
 
-        HashSet<Account> processedAccounts = new HashSet<Account>();
-        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) {
-                    Account curAccount = authority.account;
+        // join the installed sync adapter with the accounts list and emit for everything
+        pw.println();
+        pw.println("Sync Status");
+        for (Account account : accounts) {
+            pw.print("  Account "); pw.print(account.name);
+                    pw.print(" "); pw.print(account.type);
+                    pw.println(":");
+            for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterType :
+                    mSyncAdapters.getAllServices()) {
+                if (!syncAdapterType.type.accountType.equals(account.type)) {
+                    continue;
+                }
 
-                    if (processedAccounts.contains(curAccount)) {
-                        continue;
-                    }
-
-                    processedAccounts.add(curAccount);
-
-                    pw.print("  Account "); pw.print(authority.account.name);
-                            pw.print(" "); pw.print(authority.account.type);
-                            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(":");
-                        final String syncable = authority.syncable > 0
-                                ? "syncable"
-                                : (authority.syncable == 0 ? "not syncable" : "not initialized");
-                        final String enabled = authority.enabled ? "enabled" : "disabled";
-                        final String delayUntil = authority.delayUntil > now
-                                ? "delay for " + ((authority.delayUntil - now) / 1000) + " sec"
-                                : "no delay required";
-                        final String backoff = authority.backoffTime > now
-                                ? "backoff for " + ((authority.backoffTime - now) / 1000)
-                                  + " sec"
-                                : "no backoff required";
-                        final String backoffDelay = authority.backoffDelay > 0
-                                ? ("the backoff increment is " + authority.backoffDelay / 1000
-                                        + " sec")
-                                : "no backoff increment";
-                        pw.println(String.format(
-                                "      settings: %s, %s, %s, %s, %s",
-                                enabled, syncable, backoff, backoffDelay, delayUntil));
-                        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);
-                        }
-                    }
+                SyncStorageEngine.AuthorityInfo settings = mSyncStorageEngine.getAuthority(
+                        account, syncAdapterType.type.authority);
+                SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(settings);
+                pw.print("    "); pw.print(settings.authority);
+                pw.println(":");
+                pw.print("      settings:");
+                pw.print(" " + (settings.syncable > 0
+                        ? "syncable"
+                        : (settings.syncable == 0 ? "not syncable" : "not initialized")));
+                pw.print(", " + (settings.enabled ? "enabled" : "disabled"));
+                if (settings.delayUntil > now) {
+                    pw.print(", delay for "
+                            + ((settings.delayUntil - now) / 1000) + " sec");
+                }
+                if (settings.backoffTime > now) {
+                    pw.print(", backoff for "
+                            + ((settings.backoffTime - now) / 1000) + " sec");
+                }
+                if (settings.backoffDelay > 0) {
+                    pw.print(", the backoff increment is " + settings.backoffDelay / 1000
+                                + " sec");
+                }
+                pw.println();
+                for (int periodicIndex = 0;
+                        periodicIndex < settings.periodicSyncs.size();
+                        periodicIndex++) {
+                    Pair<Bundle, Long> info = settings.periodicSyncs.get(periodicIndex);
+                    long lastPeriodicTime = status.getPeriodicSyncTime(periodicIndex);
+                    long nextPeriodicTime = lastPeriodicTime + info.second * 1000;
+                    pw.println("      periodic period=" + info.second
+                            + ", extras=" + info.first
+                            + ", next=" + formatTime(nextPeriodicTime));
+                }
+                pw.print("      count: local="); pw.print(status.numSourceLocal);
+                pw.print(" poll="); pw.print(status.numSourcePoll);
+                pw.print(" periodic="); pw.print(status.numSourcePeriodic);
+                pw.print(" server="); pw.print(status.numSourceServer);
+                pw.print(" user="); pw.print(status.numSourceUser);
+                pw.print(" total="); pw.print(status.numSyncs);
+                pw.println();
+                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));
+                }
+                if (status.lastFailureTime != 0) {
+                    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);
                 }
             }
         }
@@ -1580,6 +1448,36 @@
             }
         }
 
+        private boolean isSyncAllowed(Account account, String authority, boolean manualSync,
+                boolean backgroundDataUsageAllowed) {
+            Account[] accounts = mAccounts;
+
+            // Sync is disabled, drop this operation.
+            if (!isSyncEnabled()) {
+                return false;
+            }
+
+            // skip the sync if the account of this operation no longer exists
+            if (accounts == null || !ArrayUtils.contains(accounts, account)) {
+                return false;
+            }
+
+            // skip the sync if it isn't manual and auto sync is disabled
+            final boolean syncAutomatically =
+                    mSyncStorageEngine.getSyncAutomatically(account, authority)
+                            && mSyncStorageEngine.getMasterSyncAutomatically();
+            if (!(manualSync || (backgroundDataUsageAllowed && syncAutomatically))) {
+                return false;
+            }
+
+            if (mSyncStorageEngine.getIsSyncable(account, authority) <= 0) {
+                // if not syncable or if the syncable is unknown (< 0), don't allow
+                return false;
+            }
+
+            return true;
+        }
+
         private void runStateSyncing() {
             // if the sync timeout has been reached then cancel it
 
@@ -1589,7 +1487,7 @@
             if (now > activeSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC) {
                 SyncOperation nextSyncOperation;
                 synchronized (mSyncQueue) {
-                    nextSyncOperation = mSyncQueue.nextReadyToRun(now);
+                    nextSyncOperation = getNextReadyToRunSyncOperation(now);
                 }
                 if (nextSyncOperation != null) {
                     Log.d(TAG, "canceling and rescheduling sync because it ran too long: "
@@ -1643,7 +1541,7 @@
             synchronized (mSyncQueue) {
                 final long now = SystemClock.elapsedRealtime();
                 while (true) {
-                    op = mSyncQueue.nextReadyToRun(now);
+                    op = getNextReadyToRunSyncOperation(now);
                     if (op == null) {
                         if (isLoggable) {
                             Log.v(TAG, "runStateIdle: no more sync operations, returning");
@@ -1655,42 +1553,9 @@
                     // from the queue now
                     mSyncQueue.remove(op);
 
-                    // Sync is disabled, drop this operation.
-                    if (!isSyncEnabled()) {
-                        if (isLoggable) {
-                            Log.v(TAG, "runStateIdle: sync disabled, dropping " + op);
-                        }
-                        continue;
-                    }
-
-                    // skip the sync if the account of this operation no longer exists
-                    if (!ArrayUtils.contains(accounts, op.account)) {
-                        if (isLoggable) {
-                            Log.v(TAG, "runStateIdle: account not present, dropping " + op);
-                        }
-                        continue;
-                    }
-
-                    // skip the sync if it isn't manual and auto sync is disabled
-                    final boolean manualSync =
-                            op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
-                    final boolean syncAutomatically =
-                            mSyncStorageEngine.getSyncAutomatically(op.account, op.authority)
-                                    && mSyncStorageEngine.getMasterSyncAutomatically();
-                    if (!(manualSync || (backgroundDataUsageAllowed && syncAutomatically))) {
-                        if (isLoggable) {
-                            Log.v(TAG, "runStateIdle: sync of this operation is not allowed, "
-                                    + "dropping " + op);
-                        }
-                        continue;
-                    }
-
-                    if (mSyncStorageEngine.getIsSyncable(op.account, op.authority) <= 0) {
-                        // if not syncable or if the syncable is unknown (< 0), don't allow
-                        if (isLoggable) {
-                            Log.v(TAG, "runStateIdle: sync of this operation is not allowed, "
-                                    + "dropping " + op);
-                        }
+                    if (!isSyncAllowed(op.account, op.authority,
+                            op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false),
+                            backgroundDataUsageAllowed)) {
                         continue;
                     }
 
@@ -1736,6 +1601,74 @@
             // MESSAGE_SERVICE_CONNECTED or MESSAGE_SERVICE_DISCONNECTED message
         }
 
+        private SyncOperation getNextPeriodicSyncOperation() {
+            final boolean backgroundDataUsageAllowed =
+                    getConnectivityManager().getBackgroundDataSetting();
+            SyncStorageEngine.AuthorityInfo best = null;
+            long bestPollTimeAbsolute = Long.MAX_VALUE;
+            Bundle bestExtras = null;
+            ArrayList<SyncStorageEngine.AuthorityInfo> infos = mSyncStorageEngine.getAuthorities();
+            for (SyncStorageEngine.AuthorityInfo info : infos) {
+                if (!isSyncAllowed(info.account, info.authority, false /* manualSync */,
+                        backgroundDataUsageAllowed)) {
+                    continue;
+                }
+                SyncStatusInfo status = mSyncStorageEngine.getStatusByAccountAndAuthority(
+                        info.account, info.authority);
+                int i = 0;
+                for (Pair<Bundle, Long> periodicSync : info.periodicSyncs) {
+                    long lastPollTimeAbsolute = status != null ? status.getPeriodicSyncTime(i) : 0;
+                    final Bundle extras = periodicSync.first;
+                    final Long periodInSeconds = periodicSync.second;
+                    long nextPollTimeAbsolute = lastPollTimeAbsolute + periodInSeconds * 1000;
+                    if (nextPollTimeAbsolute < bestPollTimeAbsolute) {
+                        best = info;
+                        bestPollTimeAbsolute = nextPollTimeAbsolute;
+                        bestExtras = extras;
+                    }
+                    i++;
+                }
+            }
+
+            if (best == null) {
+                return null;
+            }
+
+            final long nowAbsolute = System.currentTimeMillis();
+            final SyncOperation syncOperation = new SyncOperation(best.account,
+                    SyncStorageEngine.SOURCE_PERIODIC,
+                    best.authority, bestExtras, 0 /* delay */);
+            syncOperation.earliestRunTime = SystemClock.elapsedRealtime()
+                    + (bestPollTimeAbsolute - nowAbsolute);
+            if (syncOperation.earliestRunTime < 0) {
+                syncOperation.earliestRunTime = 0;
+            }
+            return syncOperation;
+        }
+
+        public Pair<SyncOperation, Long> bestSyncOperationCandidate() {
+            Pair<SyncOperation, Long> nextOpAndRunTime = mSyncQueue.nextOperation();
+            SyncOperation nextOp = nextOpAndRunTime != null ? nextOpAndRunTime.first : null;
+            Long nextRunTime = nextOpAndRunTime != null ? nextOpAndRunTime.second : null;
+            SyncOperation pollOp = getNextPeriodicSyncOperation();
+            if (nextOp != null
+                    && (pollOp == null || nextOp.expedited
+                        || nextRunTime <= pollOp.earliestRunTime)) {
+                return nextOpAndRunTime;
+            } else if (pollOp != null) {
+                return Pair.create(pollOp, pollOp.earliestRunTime);
+            } else {
+                return null;
+            }
+        }
+
+        private SyncOperation getNextReadyToRunSyncOperation(long now) {
+            Pair<SyncOperation, Long> nextOpAndRunTime = bestSyncOperationCandidate();
+            return nextOpAndRunTime != null && nextOpAndRunTime.second <= now
+                    ? nextOpAndRunTime.first
+                    : null;
+        }
+
         private void runBoundToSyncAdapter(ISyncAdapter syncAdapter) {
             mActiveSyncContext.mSyncAdapter = syncAdapter;
             final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
@@ -1961,7 +1894,8 @@
             ActiveSyncContext activeSyncContext = mActiveSyncContext;
             if (activeSyncContext == null) {
                 synchronized (mSyncQueue) {
-                    alarmTime = mSyncQueue.nextRunTime(now);
+                    Pair<SyncOperation, Long> candidate = bestSyncOperationCandidate();
+                    alarmTime = candidate != null ? candidate.second : 0;
                 }
             } else {
                 final long notificationTime =
@@ -2102,9 +2036,22 @@
                                 SyncStorageEngine.EVENT_STOP, syncOperation.syncSource,
                                 syncOperation.account.name.hashCode());
 
-            mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime, resultMessage,
-                    downstreamActivity, upstreamActivity);
+            mSyncStorageEngine.stopSyncEvent(rowId, syncOperation.extras, elapsedTime,
+                    resultMessage, downstreamActivity, upstreamActivity);
         }
     }
 
+    public static long runTimeWithBackoffs(SyncStorageEngine syncStorageEngine,
+            Account account, String authority, boolean isManualSync, long runTime) {
+        // if this is a manual sync, the run time is unchanged
+        // otherwise, the run time is the max of the backoffs and the run time.
+        if (isManualSync) {
+            return runTime;
+        }
+
+        Pair<Long, Long> backoff = syncStorageEngine.getBackoff(account, authority);
+        long delayUntilTime = syncStorageEngine.getDelayUntilTime(account, authority);
+
+        return Math.max(Math.max(runTime, delayUntilTime), backoff != null ? backoff.first : 0);
+    }
 }
diff --git a/core/java/android/content/SyncQueue.java b/core/java/android/content/SyncQueue.java
index a9f15d9..2eead3a 100644
--- a/core/java/android/content/SyncQueue.java
+++ b/core/java/android/content/SyncQueue.java
@@ -2,8 +2,6 @@
 
 import com.google.android.collect.Maps;
 
-import android.os.Bundle;
-import android.os.SystemClock;
 import android.util.Pair;
 import android.util.Log;
 import android.accounts.Account;
@@ -32,10 +30,9 @@
         final int N = ops.size();
         for (int i=0; i<N; i++) {
             SyncStorageEngine.PendingOperation op = ops.get(i);
-            // -1 is a special value that means expedited
-            final int delay = op.expedited ? -1 : 0;
             SyncOperation syncOperation = new SyncOperation(
-                    op.account, op.syncSource, op.authority, op.extras, delay);
+                    op.account, op.syncSource, op.authority, op.extras, 0 /* delay */);
+            syncOperation.expedited = op.expedited;
             syncOperation.pendingOperation = op;
             add(syncOperation, op);
         }
@@ -90,8 +87,15 @@
         return true;
     }
 
+    /**
+     * Remove the specified operation if it is in the queue.
+     * @param operation the operation to remove
+     */
     public void remove(SyncOperation operation) {
         SyncOperation operationToRemove = mOperationsMap.remove(operation.key);
+        if (operationToRemove == null) {
+            return;
+        }
         if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) {
             final String errorMessage = "unable to find pending row for " + operationToRemove;
             Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
@@ -102,54 +106,30 @@
      * Find the operation that should run next. Operations are sorted by their earliestRunTime,
      * prioritizing expedited operations. The earliestRunTime is adjusted by the sync adapter's
      * backoff and delayUntil times, if any.
-     * @param now the current {@link android.os.SystemClock#elapsedRealtime()}
      * @return the operation that should run next and when it should run. The time may be in
      * the future. It is expressed in milliseconds since boot.
      */
-    private Pair<SyncOperation, Long> nextOperation(long now) {
-        SyncOperation lowestOp = null;
-        long lowestOpRunTime = 0;
+    public Pair<SyncOperation, Long> nextOperation() {
+        SyncOperation best = null;
+        long bestRunTime = 0;
         for (SyncOperation op : mOperationsMap.values()) {
-            // effectiveRunTime:
-            //   - backoffTime > currentTime : backoffTime
-            //   - backoffTime <= currentTime : op.runTime
-            Pair<Long, Long> backoff = null;
-            long delayUntilTime = 0;
-            final boolean isManualSync =
-                    op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
-            if (!isManualSync) {
-                backoff = mSyncStorageEngine.getBackoff(op.account, op.authority);
-                delayUntilTime = mSyncStorageEngine.getDelayUntilTime(op.account, op.authority);
-            }
-            long backoffTime = Math.max(backoff != null ? backoff.first : 0, delayUntilTime);
-            long opRunTime = backoffTime > now ? backoffTime : op.earliestRunTime;
-            if (lowestOp == null
-                    || (lowestOp.expedited == op.expedited
-                        ? opRunTime < lowestOpRunTime
-                        : op.expedited)) {
-                lowestOp = op;
-                lowestOpRunTime = opRunTime;
+            long opRunTime = SyncManager.runTimeWithBackoffs(mSyncStorageEngine, op.account,
+                    op.authority,
+                    op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false),
+                    op.earliestRunTime);
+            // if the expedited state of both ops are the same then compare their runtime.
+            // Otherwise the candidate is only better than the current best if the candidate
+            // is expedited.
+            if (best == null
+                    || (best.expedited == op.expedited ? opRunTime < bestRunTime : op.expedited)) {
+                best = op;
+                bestRunTime = opRunTime;
             }
         }
-        if (lowestOp == null) {
+        if (best == null) {
             return null;
         }
-        return Pair.create(lowestOp, lowestOpRunTime);
-    }
-
-    /**
-     * Return when the next SyncOperation will be ready to run or null if there are
-     * none.
-     * @param now the current {@link android.os.SystemClock#elapsedRealtime()}, used to
-     * decide if the sync operation is ready to run
-     * @return when the next SyncOperation will be ready to run, expressed in elapsedRealtime()
-     */
-    public Long nextRunTime(long now) {
-        Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation(now);
-        if (nextOpAndRunTime == null) {
-            return null;
-        }
-        return nextOpAndRunTime.second;
+        return Pair.create(best, bestRunTime);
     }
 
     /**
@@ -158,21 +138,25 @@
      * decide if the sync operation is ready to run
      * @return the SyncOperation that should be run next and is ready to run.
      */
-    public SyncOperation nextReadyToRun(long now) {
-        Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation(now);
+    public Pair<SyncOperation, Long> nextReadyToRun(long now) {
+        Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation();
         if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) {
             return null;
         }
-        return nextOpAndRunTime.first;
+        return nextOpAndRunTime;
     }
 
-    public void clear(Account account, String authority) {
+    public void remove(Account account, String authority) {
         Iterator<Map.Entry<String, SyncOperation>> entries = mOperationsMap.entrySet().iterator();
         while (entries.hasNext()) {
             Map.Entry<String, SyncOperation> entry = entries.next();
             SyncOperation syncOperation = entry.getValue();
-            if (account != null && !syncOperation.account.equals(account)) continue;
-            if (authority != null && !syncOperation.authority.equals(authority)) continue;
+            if (account != null && !syncOperation.account.equals(account)) {
+                continue;
+            }
+            if (authority != null && !syncOperation.authority.equals(authority)) {
+                continue;
+            }
             entries.remove();
             if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) {
                 final String errorMessage = "unable to find pending row for " + syncOperation;
diff --git a/core/java/android/content/SyncStatusInfo.java b/core/java/android/content/SyncStatusInfo.java
index b8fda03..bb2b2da 100644
--- a/core/java/android/content/SyncStatusInfo.java
+++ b/core/java/android/content/SyncStatusInfo.java
@@ -20,10 +20,12 @@
 import android.os.Parcelable;
 import android.util.Log;
 
+import java.util.ArrayList;
+
 /** @hide */
 public class SyncStatusInfo implements Parcelable {
-    static final int VERSION = 1;
-    
+    static final int VERSION = 2;
+
     public final int authorityId;
     public long totalElapsedTime;
     public int numSyncs;
@@ -31,6 +33,7 @@
     public int numSourceServer;
     public int numSourceLocal;
     public int numSourceUser;
+    public int numSourcePeriodic;
     public long lastSuccessTime;
     public int lastSuccessSource;
     public long lastFailureTime;
@@ -39,7 +42,10 @@
     public long initialFailureTime;
     public boolean pending;
     public boolean initialize;
-    
+    public ArrayList<Long> periodicSyncTimes;
+
+    private static final String TAG = "Sync";
+
     SyncStatusInfo(int authorityId) {
         this.authorityId = authorityId;
     }
@@ -50,10 +56,11 @@
                 return Integer.parseInt(lastFailureMesg);
             }
         } catch (NumberFormatException e) {
+            Log.d(TAG, "error parsing lastFailureMesg of " + lastFailureMesg, e);
         }
         return def;
     }
-    
+
     public int describeContents() {
         return 0;
     }
@@ -75,11 +82,19 @@
         parcel.writeLong(initialFailureTime);
         parcel.writeInt(pending ? 1 : 0);
         parcel.writeInt(initialize ? 1 : 0);
+        if (periodicSyncTimes != null) {
+            parcel.writeInt(periodicSyncTimes.size());
+            for (long periodicSyncTime : periodicSyncTimes) {
+                parcel.writeLong(periodicSyncTime);
+            }
+        } else {
+            parcel.writeInt(-1);
+        }
     }
 
     SyncStatusInfo(Parcel parcel) {
         int version = parcel.readInt();
-        if (version != VERSION) {
+        if (version != VERSION && version != 1) {
             Log.w("SyncStatusInfo", "Unknown version: " + version);
         }
         authorityId = parcel.readInt();
@@ -97,8 +112,51 @@
         initialFailureTime = parcel.readLong();
         pending = parcel.readInt() != 0;
         initialize = parcel.readInt() != 0;
+        if (version == 1) {
+            periodicSyncTimes = null;
+        } else {
+            int N = parcel.readInt();
+            if (N < 0) {
+                periodicSyncTimes = null;
+            } else {
+                periodicSyncTimes = new ArrayList<Long>();
+                for (int i=0; i<N; i++) {
+                    periodicSyncTimes.add(parcel.readLong());
+                }
+            }
+        }
     }
-    
+
+    public void setPeriodicSyncTime(int index, long when) {
+        ensurePeriodicSyncTimeSize(index);
+        periodicSyncTimes.set(index, when);
+    }
+
+    private void ensurePeriodicSyncTimeSize(int index) {
+        if (periodicSyncTimes == null) {
+            periodicSyncTimes = new ArrayList<Long>(0);
+        }
+
+        final int requiredSize = index + 1;
+        if (periodicSyncTimes.size() < requiredSize) {
+            for (int i = periodicSyncTimes.size(); i < requiredSize; i++) {
+                periodicSyncTimes.add((long) 0);
+            }
+        }
+    }
+
+    public long getPeriodicSyncTime(int index) {
+        if (periodicSyncTimes == null || periodicSyncTimes.size() < (index + 1)) {
+            return 0;
+        }
+        return periodicSyncTimes.get(index);
+    }
+
+    public void removePeriodicSyncTime(int index) {
+        ensurePeriodicSyncTimeSize(index);
+        periodicSyncTimes.remove(index);
+    }
+
     public static final Creator<SyncStatusInfo> CREATOR = new Creator<SyncStatusInfo>() {
         public SyncStatusInfo createFromParcel(Parcel in) {
             return new SyncStatusInfo(in);
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index db70096..07a1f46 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -36,7 +36,6 @@
 import android.os.Parcel;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.Xml;
@@ -50,6 +49,7 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.TimeZone;
+import java.util.List;
 
 /**
  * Singleton that tracks the sync data and overall sync
@@ -62,6 +62,8 @@
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_FILE = false;
 
+    private static final long DEFAULT_POLL_FREQUENCY_SECONDS = 60 * 60 * 24; // One day
+
     // @VisibleForTesting
     static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4;
 
@@ -89,6 +91,9 @@
     /** Enum value for a user-initiated sync. */
     public static final int SOURCE_USER = 3;
 
+    /** Enum value for a periodic sync. */
+    public static final int SOURCE_PERIODIC = 4;
+
     public static final long NOT_IN_BACKOFF_MODE = -1;
 
     private static final Intent SYNC_CONNECTION_SETTING_CHANGED_INTENT =
@@ -99,7 +104,8 @@
     public static final String[] SOURCES = { "SERVER",
                                              "LOCAL",
                                              "POLL",
-                                             "USER" };
+                                             "USER",
+                                             "PERIODIC" };
 
     // The MESG column will contain one of these or one of the Error types.
     public static final String MESG_SUCCESS = "success";
@@ -164,6 +170,7 @@
         long backoffTime;
         long backoffDelay;
         long delayUntil;
+        final ArrayList<Pair<Bundle, Long>> periodicSyncs;
 
         AuthorityInfo(Account account, String authority, int ident) {
             this.account = account;
@@ -173,6 +180,8 @@
             syncable = -1; // default to "unknown"
             backoffTime = -1; // if < 0 then we aren't in backoff mode
             backoffDelay = -1; // if < 0 then we aren't in backoff mode
+            periodicSyncs = new ArrayList<Pair<Bundle, Long>>();
+            periodicSyncs.add(Pair.create(new Bundle(), DEFAULT_POLL_FREQUENCY_SECONDS));
         }
     }
 
@@ -228,6 +237,7 @@
     private int mYearInDays;
 
     private final Context mContext;
+
     private static volatile SyncStorageEngine sSyncStorageEngine = null;
 
     /**
@@ -262,17 +272,15 @@
     private int mNextHistoryId = 0;
     private boolean mMasterSyncAutomatically = true;
 
-    private SyncStorageEngine(Context context) {
+    private SyncStorageEngine(Context context, File dataDir) {
         mContext = context;
         sSyncStorageEngine = this;
 
         mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
 
-        // This call will return the correct directory whether Encrypted File Systems is
-        // enabled or not.
-        File dataDir = Environment.getSecureDataDirectory();
         File systemDir = new File(dataDir, "system");
         File syncDir = new File(systemDir, "sync");
+        syncDir.mkdirs();
         mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
         mStatusFile = new AtomicFile(new File(syncDir, "status.bin"));
         mPendingFile = new AtomicFile(new File(syncDir, "pending.bin"));
@@ -286,14 +294,17 @@
     }
 
     public static SyncStorageEngine newTestInstance(Context context) {
-        return new SyncStorageEngine(context);
+        return new SyncStorageEngine(context, context.getFilesDir());
     }
 
     public static void init(Context context) {
         if (sSyncStorageEngine != null) {
             return;
         }
-        sSyncStorageEngine = new SyncStorageEngine(context);
+        // This call will return the correct directory whether Encrypted File Systems is
+        // enabled or not.
+        File dataDir = Environment.getSecureDataDirectory();
+        sSyncStorageEngine = new SyncStorageEngine(context, dataDir);
     }
 
     public static SyncStorageEngine getSingleton() {
@@ -475,7 +486,7 @@
                 }
             } else {
                 AuthorityInfo authority =
-                        getOrCreateAuthorityLocked(account, providerName, -1, false);
+                        getOrCreateAuthorityLocked(account, providerName, -1 /* ident */, true);
                 if (authority.backoffTime == nextSyncTime && authority.backoffDelay == nextDelay) {
                     return;
                 }
@@ -483,9 +494,6 @@
                 authority.backoffDelay = nextDelay;
                 changed = true;
             }
-            if (changed) {
-                writeAccountInfoLocked();
-            }
         }
 
         if (changed) {
@@ -499,12 +507,12 @@
                     + " -> delayUntil " + delayUntil);
         }
         synchronized (mAuthorities) {
-            AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false);
+            AuthorityInfo authority = getOrCreateAuthorityLocked(
+                    account, providerName, -1 /* ident */, true);
             if (authority.delayUntil == delayUntil) {
                 return;
             }
             authority.delayUntil = delayUntil;
-            writeAccountInfoLocked();
         }
 
         reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
@@ -520,6 +528,90 @@
         }
     }
 
+    private void updateOrRemovePeriodicSync(Account account, String providerName, Bundle extras,
+            long period, boolean add) {
+        if (period <= 0) {
+            period = 0;
+        }
+        if (extras == null) {
+            extras = new Bundle();
+        }
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "addOrRemovePeriodicSync: " + account + ", provider " + providerName
+                    + " -> period " + period + ", extras " + extras);
+        }
+        synchronized (mAuthorities) {
+            AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false);
+            if (add) {
+                boolean alreadyPresent = false;
+                for (int i = 0, N = authority.periodicSyncs.size(); i < N; i++) {
+                    Pair<Bundle, Long> syncInfo = authority.periodicSyncs.get(i);
+                    final Bundle existingExtras = syncInfo.first;
+                    if (equals(existingExtras, extras)) {
+                        if (syncInfo.second == period) {
+                            return;
+                        }
+                        authority.periodicSyncs.set(i, Pair.create(extras, period));
+                        alreadyPresent = true;
+                        break;
+                    }
+                }
+                if (!alreadyPresent) {
+                    authority.periodicSyncs.add(Pair.create(extras, period));
+                    SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
+                    status.setPeriodicSyncTime(authority.periodicSyncs.size() - 1, 0);
+                }
+            } else {
+                SyncStatusInfo status = mSyncStatus.get(authority.ident);
+                boolean changed = false;
+                Iterator<Pair<Bundle, Long>> iterator = authority.periodicSyncs.iterator();
+                int i = 0;
+                while (iterator.hasNext()) {
+                    Pair<Bundle, Long> syncInfo = iterator.next();
+                    if (equals(syncInfo.first, extras)) {
+                        iterator.remove();
+                        changed = true;
+                        if (status != null) {
+                            status.removePeriodicSyncTime(i);
+                        }
+                    } else {
+                        i++;
+                    }
+                }
+                if (!changed) {
+                    return;
+                }
+            }
+            writeAccountInfoLocked();
+            writeStatusLocked();
+        }
+
+        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+    }
+
+    public void addPeriodicSync(Account account, String providerName, Bundle extras,
+            long pollFrequency) {
+        updateOrRemovePeriodicSync(account, providerName, extras, pollFrequency, true /* add */);
+    }
+
+    public void removePeriodicSync(Account account, String providerName, Bundle extras) {
+        updateOrRemovePeriodicSync(account, providerName, extras, 0 /* period, ignored */,
+                false /* remove */);
+    }
+
+    public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) {
+        ArrayList<PeriodicSync> syncs = new ArrayList<PeriodicSync>();
+        synchronized (mAuthorities) {
+            AuthorityInfo authority = getAuthorityLocked(account, providerName, "getPeriodicSyncs");
+            if (authority != null) {
+                for (Pair<Bundle, Long> item : authority.periodicSyncs) {
+                    syncs.add(new PeriodicSync(account, providerName, item.first, item.second));
+                }
+            }
+        }
+        return syncs;
+    }
+
     public void setMasterSyncAutomatically(boolean flag) {
         boolean old;
         synchronized (mAuthorities) {
@@ -817,7 +909,25 @@
         return id;
     }
 
-    public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage,
+    public static boolean equals(Bundle b1, Bundle b2) {
+        if (b1.size() != b2.size()) {
+            return false;
+        }
+        if (b1.isEmpty()) {
+            return true;
+        }
+        for (String key : b1.keySet()) {
+            if (!b2.containsKey(key)) {
+                return false;
+            }
+            if (!b1.get(key).equals(b2.get(key))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public void stopSyncEvent(long historyId, Bundle extras, long elapsedTime, String resultMessage,
             long downstreamActivity, long upstreamActivity) {
         synchronized (mAuthorities) {
             if (DEBUG) Log.v(TAG, "stopSyncEvent: historyId=" + historyId);
@@ -860,6 +970,17 @@
                 case SOURCE_SERVER:
                     status.numSourceServer++;
                     break;
+                case SOURCE_PERIODIC:
+                    status.numSourcePeriodic++;
+                    AuthorityInfo authority = mAuthorities.get(item.authorityId);
+                    for (int periodicSyncIndex = 0;
+                            periodicSyncIndex < authority.periodicSyncs.size();
+                            periodicSyncIndex++) {
+                        if (equals(extras, authority.periodicSyncs.get(periodicSyncIndex).first)) {
+                            status.setPeriodicSyncTime(periodicSyncIndex, item.eventTime);
+                        }
+                    }
+                    break;
             }
 
             boolean writeStatisticsNow = false;
@@ -948,11 +1069,27 @@
     }
 
     /**
+     * Return an array of the current authorities. Note
+     * that the objects inside the array are the real, live objects,
+     * so be careful what you do with them.
+     */
+    public ArrayList<AuthorityInfo> getAuthorities() {
+        synchronized (mAuthorities) {
+            final int N = mAuthorities.size();
+            ArrayList<AuthorityInfo> infos = new ArrayList<AuthorityInfo>(N);
+            for (int i=0; i<N; i++) {
+                infos.add(mAuthorities.valueAt(i));
+            }
+            return infos;
+        }
+    }
+
+    /**
      * Returns the status that matches the authority and account.
      *
      * @param account the account we want to check
      * @param authority the authority whose row should be selected
-     * @return the SyncStatusInfo for the authority, or null if none exists
+     * @return the SyncStatusInfo for the authority
      */
     public SyncStatusInfo getStatusByAccountAndAuthority(Account account, String authority) {
         if (account == null || authority == null) {
@@ -1130,6 +1267,12 @@
         return authority;
     }
 
+    public SyncStatusInfo getOrCreateSyncStatus(AuthorityInfo authority) {
+        synchronized (mAuthorities) {
+            return getOrCreateSyncStatusLocked(authority.ident);
+        }
+    }
+
     private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) {
         SyncStatusInfo status = mSyncStatus.get(authorityId);
         if (status == null) {
@@ -1155,6 +1298,25 @@
     }
 
     /**
+     * public for testing
+     */
+    public void clearAndReadState() {
+        synchronized (mAuthorities) {
+            mAuthorities.clear();
+            mAccounts.clear();
+            mPendingOperations.clear();
+            mSyncStatus.clear();
+            mSyncHistory.clear();
+
+            readAccountInfoLocked();
+            readStatusLocked();
+            readPendingOperationsLocked();
+            readStatisticsLocked();
+            readLegacyAccountInfoLocked();
+        }
+    }
+
+    /**
      * Read all account information back in to the initial engine state.
      */
     private void readAccountInfoLocked() {
@@ -1175,59 +1337,23 @@
                 mMasterSyncAutomatically = listen == null
                             || Boolean.parseBoolean(listen);
                 eventType = parser.next();
+                AuthorityInfo authority = null;
+                Pair<Bundle, Long> periodicSync = null;
                 do {
-                    if (eventType == XmlPullParser.START_TAG
-                            && parser.getDepth() == 2) {
+                    if (eventType == XmlPullParser.START_TAG) {
                         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 (parser.getDepth() == 2) {
+                            if ("authority".equals(tagName)) {
+                                authority = parseAuthority(parser);
+                                periodicSync = null;
                             }
-                            if (id >= 0) {
-                                String accountName = parser.getAttributeValue(
-                                        null, "account");
-                                String accountType = parser.getAttributeValue(
-                                        null, "type");
-                                if (accountType == null) {
-                                    accountType = "com.google";
-                                }
-                                String authorityName = parser.getAttributeValue(
-                                        null, "authority");
-                                String enabled = parser.getAttributeValue(
-                                        null, "enabled");
-                                String syncable = parser.getAttributeValue(null, "syncable");
-                                AuthorityInfo authority = mAuthorities.get(id);
-                                if (DEBUG_FILE) Log.v(TAG, "Adding authority: account="
-                                        + accountName + " auth=" + authorityName
-                                        + " enabled=" + enabled
-                                        + " syncable=" + syncable);
-                                if (authority == null) {
-                                    if (DEBUG_FILE) Log.v(TAG, "Creating entry");
-                                    authority = getOrCreateAuthorityLocked(
-                                            new Account(accountName, accountType),
-                                            authorityName, id, false);
-                                }
-                                if (authority != null) {
-                                    authority.enabled = enabled == null
-                                            || Boolean.parseBoolean(enabled);
-                                    if ("unknown".equals(syncable)) {
-                                        authority.syncable = -1;
-                                    } else {
-                                        authority.syncable =
-                                                (syncable == null || Boolean.parseBoolean(enabled))
-                                                        ? 1
-                                                        : 0;
-                                    }
-                                } else {
-                                    Log.w(TAG, "Failure adding authority: account="
-                                            + accountName + " auth=" + authorityName
-                                            + " enabled=" + enabled
-                                            + " syncable=" + syncable);
-                                }
+                        } else if (parser.getDepth() == 3) {
+                            if ("periodicSync".equals(tagName) && authority != null) {
+                                periodicSync = parsePeriodicSync(parser, authority);
+                            }
+                        } else if (parser.getDepth() == 4 && periodicSync != null) {
+                            if ("extra".equals(tagName)) {
+                                parseExtra(parser, periodicSync);
                             }
                         }
                     }
@@ -1249,6 +1375,105 @@
         }
     }
 
+    private AuthorityInfo parseAuthority(XmlPullParser parser) {
+        AuthorityInfo authority = null;
+        int id = -1;
+        try {
+            id = Integer.parseInt(parser.getAttributeValue(
+                    null, "id"));
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "error parsing the id of the authority", e);
+        } catch (NullPointerException e) {
+            Log.e(TAG, "the id of the authority is null", e);
+        }
+        if (id >= 0) {
+            String accountName = parser.getAttributeValue(null, "account");
+            String accountType = parser.getAttributeValue(null, "type");
+            if (accountType == null) {
+                accountType = "com.google";
+            }
+            String authorityName = parser.getAttributeValue(null, "authority");
+            String enabled = parser.getAttributeValue(null, "enabled");
+            String syncable = parser.getAttributeValue(null, "syncable");
+            authority = mAuthorities.get(id);
+            if (DEBUG_FILE) Log.v(TAG, "Adding authority: account="
+                    + accountName + " auth=" + authorityName
+                    + " enabled=" + enabled
+                    + " syncable=" + syncable);
+            if (authority == null) {
+                if (DEBUG_FILE) Log.v(TAG, "Creating entry");
+                authority = getOrCreateAuthorityLocked(
+                        new Account(accountName, accountType), authorityName, id, false);
+                // clear this since we will read these later on
+                authority.periodicSyncs.clear();
+            }
+            if (authority != null) {
+                authority.enabled = enabled == null || Boolean.parseBoolean(enabled);
+                if ("unknown".equals(syncable)) {
+                    authority.syncable = -1;
+                } else {
+                    authority.syncable =
+                            (syncable == null || Boolean.parseBoolean(enabled)) ? 1 : 0;
+                }
+            } else {
+                Log.w(TAG, "Failure adding authority: account="
+                        + accountName + " auth=" + authorityName
+                        + " enabled=" + enabled
+                        + " syncable=" + syncable);
+            }
+        }
+
+        return authority;
+    }
+
+    private Pair<Bundle, Long> parsePeriodicSync(XmlPullParser parser, AuthorityInfo authority) {
+        Bundle extras = new Bundle();
+        String periodValue = parser.getAttributeValue(null, "period");
+        final long period;
+        try {
+            period = Long.parseLong(periodValue);
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "error parsing the period of a periodic sync", e);
+            return null;
+        } catch (NullPointerException e) {
+            Log.e(TAG, "the period of a periodic sync is null", e);
+            return null;
+        }
+        final Pair<Bundle, Long> periodicSync = Pair.create(extras, period);
+        authority.periodicSyncs.add(periodicSync);
+        return periodicSync;
+    }
+
+    private void parseExtra(XmlPullParser parser, Pair<Bundle, Long> periodicSync) {
+        final Bundle extras = periodicSync.first;
+        String name = parser.getAttributeValue(null, "name");
+        String type = parser.getAttributeValue(null, "type");
+        String value1 = parser.getAttributeValue(null, "value1");
+        String value2 = parser.getAttributeValue(null, "value2");
+
+        try {
+            if ("long".equals(type)) {
+                extras.putLong(name, Long.parseLong(value1));
+            } else if ("integer".equals(type)) {
+                extras.putInt(name, Integer.parseInt(value1));
+            } else if ("double".equals(type)) {
+                extras.putDouble(name, Double.parseDouble(value1));
+            } else if ("float".equals(type)) {
+                extras.putFloat(name, Float.parseFloat(value1));
+            } else if ("boolean".equals(type)) {
+                extras.putBoolean(name, Boolean.parseBoolean(value1));
+            } else if ("string".equals(type)) {
+                extras.putString(name, value1);
+            } else if ("account".equals(type)) {
+                extras.putParcelable(name, new Account(value1, value2));
+            }
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "error parsing bundle value", e);
+        } catch (NullPointerException e) {
+            Log.e(TAG, "error parsing bundle value", e);
+        }
+    }
+
     /**
      * Write all account information to the account file.
      */
@@ -1284,6 +1509,41 @@
                 } else if (authority.syncable == 0) {
                     out.attribute(null, "syncable", "false");
                 }
+                for (Pair<Bundle, Long> periodicSync : authority.periodicSyncs) {
+                    out.startTag(null, "periodicSync");
+                    out.attribute(null, "period", Long.toString(periodicSync.second));
+                    final Bundle extras = periodicSync.first;
+                    for (String key : extras.keySet()) {
+                        out.startTag(null, "extra");
+                        out.attribute(null, "name", key);
+                        final Object value = extras.get(key);
+                        if (value instanceof Long) {
+                            out.attribute(null, "type", "long");
+                            out.attribute(null, "value1", value.toString());
+                        } else if (value instanceof Integer) {
+                            out.attribute(null, "type", "integer");
+                            out.attribute(null, "value1", value.toString());
+                        } else if (value instanceof Boolean) {
+                            out.attribute(null, "type", "boolean");
+                            out.attribute(null, "value1", value.toString());
+                        } else if (value instanceof Float) {
+                            out.attribute(null, "type", "float");
+                            out.attribute(null, "value1", value.toString());
+                        } else if (value instanceof Double) {
+                            out.attribute(null, "type", "double");
+                            out.attribute(null, "value1", value.toString());
+                        } else if (value instanceof String) {
+                            out.attribute(null, "type", "string");
+                            out.attribute(null, "value1", value.toString());
+                        } else if (value instanceof Account) {
+                            out.attribute(null, "type", "account");
+                            out.attribute(null, "value1", ((Account)value).name);
+                            out.attribute(null, "value2", ((Account)value).type);
+                        }
+                        out.endTag(null, "extra");
+                    }
+                    out.endTag(null, "periodicSync");
+                }
                 out.endTag(null, "authority");
             }
 
@@ -1389,6 +1649,7 @@
                     st.numSourcePoll = getIntColumn(c, "numSourcePoll");
                     st.numSourceServer = getIntColumn(c, "numSourceServer");
                     st.numSourceUser = getIntColumn(c, "numSourceUser");
+                    st.numSourcePeriodic = 0;
                     st.lastSuccessSource = getIntColumn(c, "lastSuccessSource");
                     st.lastSuccessTime = getLongColumn(c, "lastSuccessTime");
                     st.lastFailureSource = getIntColumn(c, "lastFailureSource");
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index f89ba91..cb42d73 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -184,6 +184,20 @@
          * <P>Type: INTEGER (long)</P>
          */
         public static final String _SYNC_DIRTY = "_sync_dirty";
+
+        /**
+         * The name of the account instance to which this row belongs, which when paired with
+         * {@link #ACCOUNT_TYPE} identifies a specific account.
+         * <P>Type: TEXT</P>
+         */
+        public static final String ACCOUNT_NAME = "account_name";
+
+        /**
+         * The type of account to which this row belongs, which when paired with
+         * {@link #ACCOUNT_NAME} identifies a specific account.
+         * <P>Type: TEXT</P>
+         */
+        public static final String ACCOUNT_TYPE = "account_type";
     }
 
     /**
@@ -579,20 +593,6 @@
         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
                 "/event_entities");
 
-        /**
-         * The name of the account instance to which this row belongs, which when paired with
-         * {@link #ACCOUNT_TYPE} identifies a specific account.
-         * <P>Type: TEXT</P>
-         */
-        public static final String ACCOUNT_NAME = "_sync_account";
-
-        /**
-         * The type of account to which this row belongs, which when paired with
-         * {@link #ACCOUNT_NAME} identifies a specific account.
-         * <P>Type: TEXT</P>
-         */
-        public static final String ACCOUNT_TYPE = "_sync_account_type";
-
         public static EntityIterator newEntityIterator(Cursor cursor, ContentResolver resolver) {
             return new EntityIteratorImpl(cursor, resolver);
         }
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 66a7631..fd6af05 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -32,7 +32,6 @@
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
@@ -307,6 +306,11 @@
      * Handles one frame of a fling
      */
     private FlingRunnable mFlingRunnable;
+    
+    /**
+     * Handles scrolling between positions within the list.
+     */
+    private PositionScroller mPositionScroller;
 
     /**
      * The offset in pixels form the top of the AdapterView to the top
@@ -1588,6 +1592,10 @@
                 // let the fling runnable report it's new state which
                 // should be idle
                 mFlingRunnable.endFling();
+                if (mScrollY != 0) {
+                    mScrollY = 0;
+                    invalidate();
+                }
             }
             // Always hide the type filter
             dismissPopup();
@@ -1935,9 +1943,13 @@
         } else {
             int touchMode = mTouchMode;
             if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
-                mScrollY = 0;
                 if (mFlingRunnable != null) {
                     mFlingRunnable.endFling();
+                    
+                    if (mScrollY != 0) {
+                        mScrollY = 0;
+                        invalidate();
+                    }
                 }
             }
         }
@@ -2052,9 +2064,11 @@
                     if (motionView != null) {
                         motionViewPrevTop = motionView.getTop();
                     }
+                    
                     // No need to do all this work if we're not going to move anyway
+                    boolean atEdge = false;
                     if (incrementalDeltaY != 0) {
-                        trackMotionScroll(deltaY, incrementalDeltaY);
+                        atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
                     }
 
                     // Check to see if we have bumped into the scroll limit
@@ -2064,7 +2078,7 @@
                         // supposed to be
                         final int motionViewRealTop = motionView.getTop();
                         final int motionViewNewTop = mMotionViewNewTop;
-                        if (motionViewRealTop != motionViewNewTop) {
+                        if (atEdge) {
                             // Apply overscroll
                             
                             mScrollY -= incrementalDeltaY - (motionViewRealTop - motionViewPrevTop);
@@ -2440,7 +2454,7 @@
                 }
             }
         }
-        
+
         void startSpringback() {
             if (mScroller.springback(0, mScrollY, 0, 0, 0, 0)) {
                 mTouchMode = TOUCH_MODE_OVERFLING;
@@ -2448,19 +2462,33 @@
                 post(this);
             }
         }
-        
+
         void startOverfling(int initialVelocity) {
             mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0, 0, 0, 0, getHeight());
             mTouchMode = TOUCH_MODE_OVERFLING;
             invalidate();
             post(this);
         }
-        
+
+        void startScroll(int distance, int duration) {
+            int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
+            mLastFlingY = initialY;
+            mScroller.startScroll(0, initialY, 0, distance, duration);
+            mTouchMode = TOUCH_MODE_FLING;
+            post(this);
+        }
+
         private void endFling() {
             mTouchMode = TOUCH_MODE_REST;
+
             reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
             clearScrollingCache();
+
             removeCallbacks(this);
+
+            if (mPositionScroller != null) {
+                removeCallbacks(mPositionScroller);
+            }
         }
 
         public void run() {
@@ -2553,6 +2581,278 @@
 
         }
     }
+    
+    
+    class PositionScroller implements Runnable {
+        private static final int SCROLL_DURATION = 400;
+        
+        private static final int MOVE_DOWN_POS = 1;
+        private static final int MOVE_UP_POS = 2;
+        private static final int MOVE_DOWN_BOUND = 3;
+        private static final int MOVE_UP_BOUND = 4;
+        
+        private int mMode;
+        private int mTargetPos;
+        private int mBoundPos;
+        private int mLastSeenPos;
+        private int mScrollDuration;
+        private int mExtraScroll;
+        
+        PositionScroller() {
+            mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
+        }
+        
+        void start(int position) {
+            final int firstPos = mFirstPosition;
+            final int lastPos = firstPos + getChildCount() - 1;
+            
+            int viewTravelCount = 0;
+            if (position <= firstPos) {                
+                viewTravelCount = firstPos - position + 1;
+                mMode = MOVE_UP_POS;
+            } else if (position >= lastPos) {
+                viewTravelCount = position - lastPos + 1;
+                mMode = MOVE_DOWN_POS;
+            } else {
+                // Already on screen, nothing to do
+                return;
+            }
+            
+            if (viewTravelCount > 0) {
+                mScrollDuration = SCROLL_DURATION / viewTravelCount;
+            } else {
+                mScrollDuration = SCROLL_DURATION;
+            }
+            mTargetPos = position;
+            mBoundPos = INVALID_POSITION;
+            mLastSeenPos = INVALID_POSITION;
+            
+            post(this);
+        }
+        
+        void start(int position, int boundPosition) {
+            if (boundPosition == INVALID_POSITION) {
+                start(position);
+                return;
+            }
+            
+            final int firstPos = mFirstPosition;
+            final int lastPos = firstPos + getChildCount() - 1;
+            
+            int viewTravelCount = 0;
+            if (position < firstPos) {
+                final int boundPosFromLast = lastPos - boundPosition;
+                if (boundPosFromLast < 1) {
+                    // Moving would shift our bound position off the screen. Abort.
+                    return;
+                }
+                
+                final int posTravel = firstPos - position + 1;
+                final int boundTravel = boundPosFromLast - 1;
+                if (boundTravel < posTravel) {
+                    viewTravelCount = boundTravel;
+                    mMode = MOVE_UP_BOUND;
+                } else {
+                    viewTravelCount = posTravel;
+                    mMode = MOVE_UP_POS;
+                }
+            } else if (position > lastPos) {
+                final int boundPosFromFirst = boundPosition - firstPos;
+                if (boundPosFromFirst < 1) {
+                    // Moving would shift our bound position off the screen. Abort.
+                    return;
+                }
+
+                final int posTravel = position - lastPos + 1;
+                final int boundTravel = boundPosFromFirst - 1;
+                if (boundTravel < posTravel) {
+                    viewTravelCount = boundTravel;
+                    mMode = MOVE_DOWN_BOUND;
+                } else {
+                    viewTravelCount = posTravel;
+                    mMode = MOVE_DOWN_POS;
+                }
+            } else {
+                // Already on screen, nothing to do
+                return;
+            }
+            
+            if (viewTravelCount > 0) {
+                mScrollDuration = SCROLL_DURATION / viewTravelCount;
+            } else {
+                mScrollDuration = SCROLL_DURATION;
+            }
+            mTargetPos = position;
+            mBoundPos = boundPosition;
+            mLastSeenPos = INVALID_POSITION;
+            
+            post(this);
+        }
+        
+        void stop() {
+            removeCallbacks(this);
+        }
+        
+        public void run() {
+            final int listHeight = getHeight();
+            final int firstPos = mFirstPosition;
+            
+            switch (mMode) {
+            case MOVE_DOWN_POS: {
+                final int lastViewIndex = getChildCount() - 1;
+                final int lastPos = firstPos + lastViewIndex;
+
+                if (lastPos == mLastSeenPos) {
+                    // No new views, let things keep going.
+                    post(this);
+                    return;
+                }
+
+                final View lastView = getChildAt(lastViewIndex);
+                final int lastViewHeight = lastView.getHeight();
+                final int lastViewTop = lastView.getTop();
+                final int lastViewPixelsShowing = listHeight - lastViewTop;
+
+                smoothScrollBy(lastViewHeight - lastViewPixelsShowing + mExtraScroll,
+                        mScrollDuration);
+
+                mLastSeenPos = lastPos;
+                if (lastPos != mTargetPos) {
+                    post(this);
+                }
+                break;
+            }
+                
+            case MOVE_DOWN_BOUND: {
+                final int nextViewIndex = 1;
+                if (firstPos == mBoundPos || getChildCount() <= nextViewIndex) {
+                    return;
+                }
+                final int nextPos = firstPos + nextViewIndex;
+
+                if (nextPos == mLastSeenPos) {
+                    // No new views, let things keep going.
+                    post(this);
+                    return;
+                }
+
+                final View nextView = getChildAt(nextViewIndex);
+                final int nextViewHeight = nextView.getHeight();
+                final int nextViewTop = nextView.getTop();
+                final int extraScroll = mExtraScroll;
+                if (nextPos != mBoundPos) {
+                    smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
+                            mScrollDuration);
+
+                    mLastSeenPos = nextPos;
+
+                    post(this);
+                } else  {
+                    if (nextViewTop > extraScroll) { 
+                        smoothScrollBy(nextViewTop - extraScroll, mScrollDuration);
+                    }
+                }
+                break;
+            }
+                
+            case MOVE_UP_POS: {
+                if (firstPos == mLastSeenPos) {
+                    // No new views, let things keep going.
+                    post(this);
+                    return;
+                }
+
+                final View firstView = getChildAt(0);
+                final int firstViewTop = firstView.getTop();
+
+                smoothScrollBy(firstViewTop - mExtraScroll, mScrollDuration);
+
+                mLastSeenPos = firstPos;
+
+                if (firstPos != mTargetPos) {
+                    post(this);
+                }
+                break;
+            }
+                
+            case MOVE_UP_BOUND: {
+                final int lastViewIndex = getChildCount() - 2;
+                if (lastViewIndex < 0) {
+                    return;
+                }
+                final int lastPos = firstPos + lastViewIndex;
+
+                if (lastPos == mLastSeenPos) {
+                    // No new views, let things keep going.
+                    post(this);
+                    return;
+                }
+
+                final View lastView = getChildAt(lastViewIndex);
+                final int lastViewHeight = lastView.getHeight();
+                final int lastViewTop = lastView.getTop();
+                final int lastViewPixelsShowing = listHeight - lastViewTop;
+                mLastSeenPos = lastPos;
+                if (lastPos != mBoundPos) {
+                    smoothScrollBy(-(lastViewPixelsShowing - mExtraScroll), mScrollDuration);
+                    post(this);
+                } else {
+                    final int bottom = listHeight - mExtraScroll;
+                    final int lastViewBottom = lastViewTop + lastViewHeight;
+                    if (bottom > lastViewBottom) {
+                        smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration);
+                    }
+                }
+                break;
+            }
+
+            default:
+                break;
+            }
+        }
+    }
+    
+    /**
+     * Smoothly scroll to the specified adapter position. The view will
+     * scroll such that the indicated position is displayed.
+     * @param position Scroll to this adapter position.
+     */
+    public void smoothScrollToPosition(int position) {
+        if (mPositionScroller == null) {
+            mPositionScroller = new PositionScroller();
+        }
+        mPositionScroller.start(position);
+    }
+    
+    /**
+     * Smoothly scroll to the specified adapter position. The view will
+     * scroll such that the indicated position is displayed, but it will
+     * stop early if scrolling further would scroll boundPosition out of
+     * view. 
+     * @param position Scroll to this adapter position.
+     * @param boundPosition Do not scroll if it would move this adapter
+     *          position out of view.
+     */
+    public void smoothScrollToPosition(int position, int boundPosition) {
+        if (mPositionScroller == null) {
+            mPositionScroller = new PositionScroller();
+        }
+        mPositionScroller.start(position, boundPosition);
+    }
+    
+    /**
+     * Smoothly scroll by distance pixels over duration milliseconds.
+     * @param distance Distance to scroll in pixels.
+     * @param duration Duration of the scroll animation in milliseconds.
+     */
+    public void smoothScrollBy(int distance, int duration) {
+        if (mFlingRunnable == null) {
+            mFlingRunnable = new FlingRunnable();
+        } else {
+            mFlingRunnable.endFling();
+        }
+        mFlingRunnable.startScroll(distance, duration);
+    }
 
     private void createScrollingCache() {
         if (mScrollingCacheEnabled && !mCachingStarted) {
@@ -2588,11 +2888,12 @@
      * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
      *        began. Positive numbers mean the user's finger is moving down the screen.
      * @param incrementalDeltaY Change in deltaY from the previous event.
+     * @return true if we're already at the beginning/end of the list and have nothing to do.
      */
-    void trackMotionScroll(int deltaY, int incrementalDeltaY) {
+    boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
         final int childCount = getChildCount();
         if (childCount == 0) {
-            return;
+            return true;
         }
 
         final int firstTop = getChildAt(0).getTop();
@@ -2618,98 +2919,99 @@
             incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
         }
 
-        final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
+        final int firstPosition = mFirstPosition;
 
-        if (spaceAbove >= absIncrementalDeltaY && spaceBelow >= absIncrementalDeltaY) {
-            hideSelector();
-            offsetChildrenTopAndBottom(incrementalDeltaY);
-            if (!awakenScrollBars()) {
-                invalidate();
-            }
-            mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
-        } else {
-            final int firstPosition = mFirstPosition;
-
-            if (firstPosition == 0 && firstTop >= listPadding.top && deltaY > 0) {
-                // Don't need to move views down if the top of the first position is already visible
-                return;
-            }
-
-            if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY < 0) {
-                // Don't need to move views up if the bottom of the last position is already visible
-                return;
-            }
-
-            final boolean down = incrementalDeltaY < 0;
-
-            hideSelector();
-
-            final int headerViewsCount = getHeaderViewsCount();
-            final int footerViewsStart = mItemCount - getFooterViewsCount();
-
-            int start = 0;
-            int count = 0;
-
-            if (down) {
-                final int top = listPadding.top - incrementalDeltaY;
-                for (int i = 0; i < childCount; i++) {
-                    final View child = getChildAt(i);
-                    if (child.getBottom() >= top) {
-                        break;
-                    } else {
-                        count++;
-                        int position = firstPosition + i;
-                        if (position >= headerViewsCount && position < footerViewsStart) {
-                            mRecycler.addScrapView(child);
-
-                            if (ViewDebug.TRACE_RECYCLER) {
-                                ViewDebug.trace(child,
-                                        ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
-                                        firstPosition + i, -1);
-                            }
-                        }
-                    }
-                }
-            } else {
-                final int bottom = getHeight() - listPadding.bottom - incrementalDeltaY;
-                for (int i = childCount - 1; i >= 0; i--) {
-                    final View child = getChildAt(i);
-                    if (child.getTop() <= bottom) {
-                        break;
-                    } else {
-                        start = i;
-                        count++;
-                        int position = firstPosition + i;
-                        if (position >= headerViewsCount && position < footerViewsStart) {
-                            mRecycler.addScrapView(child);
-
-                            if (ViewDebug.TRACE_RECYCLER) {
-                                ViewDebug.trace(child,
-                                        ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
-                                        firstPosition + i, -1);
-                            }
-                        }
-                    }
-                }
-            }
-
-            mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
-
-            mBlockLayoutRequests = true;
-            detachViewsFromParent(start, count);
-            offsetChildrenTopAndBottom(incrementalDeltaY);
-
-            if (down) {
-                mFirstPosition += count;
-            }
-
-            invalidate();
-            fillGap(down);
-            mBlockLayoutRequests = false;
-
-            invokeOnItemScrollListener();
-            awakenScrollBars();
+        if (firstPosition == 0 && firstTop >= listPadding.top && deltaY > 0) {
+            // Don't need to move views down if the top of the first position
+            // is already visible
+            return true;
         }
+
+        if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY < 0) {
+            // Don't need to move views up if the bottom of the last position
+            // is already visible
+            return true;
+        }
+
+        final boolean down = incrementalDeltaY < 0;
+
+        hideSelector();
+
+        final int headerViewsCount = getHeaderViewsCount();
+        final int footerViewsStart = mItemCount - getFooterViewsCount();
+
+        int start = 0;
+        int count = 0;
+
+        if (down) {
+            final int top = listPadding.top - incrementalDeltaY;
+            for (int i = 0; i < childCount; i++) {
+                final View child = getChildAt(i);
+                if (child.getBottom() >= top) {
+                    break;
+                } else {
+                    count++;
+                    int position = firstPosition + i;
+                    if (position >= headerViewsCount && position < footerViewsStart) {
+                        mRecycler.addScrapView(child);
+
+                        if (ViewDebug.TRACE_RECYCLER) {
+                            ViewDebug.trace(child,
+                                    ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
+                                    firstPosition + i, -1);
+                        }
+                    }
+                }
+            }
+        } else {
+            final int bottom = getHeight() - listPadding.bottom - incrementalDeltaY;
+            for (int i = childCount - 1; i >= 0; i--) {
+                final View child = getChildAt(i);
+                if (child.getTop() <= bottom) {
+                    break;
+                } else {
+                    start = i;
+                    count++;
+                    int position = firstPosition + i;
+                    if (position >= headerViewsCount && position < footerViewsStart) {
+                        mRecycler.addScrapView(child);
+
+                        if (ViewDebug.TRACE_RECYCLER) {
+                            ViewDebug.trace(child,
+                                    ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
+                                    firstPosition + i, -1);
+                        }
+                    }
+                }
+            }
+        }
+
+        mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
+
+        mBlockLayoutRequests = true;
+
+        if (count > 0) {
+            detachViewsFromParent(start, count);
+        }
+        offsetChildrenTopAndBottom(incrementalDeltaY);
+
+        if (down) {
+            mFirstPosition += count;
+        }
+
+        invalidate();
+
+        final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
+        if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
+            fillGap(down);
+        }
+
+        mBlockLayoutRequests = false;
+
+        invokeOnItemScrollListener();
+        awakenScrollBars();
+        
+        return false;
     }
 
     /**
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index 405461a..a4b20da 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -18,14 +18,12 @@
 
 import com.android.internal.R;
 
-import java.util.ArrayList;
-
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
 import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.AttributeSet;
@@ -35,6 +33,8 @@
 import android.view.ContextMenu.ContextMenuInfo;
 import android.widget.ExpandableListConnector.PositionMetadata;
 
+import java.util.ArrayList;
+
 /**
  * A view that shows items in a vertically scrolling two-level list. This
  * differs from the {@link ListView} by allowing two levels: groups which can
@@ -541,6 +541,12 @@
                 if (mOnGroupExpandListener != null) {
                     mOnGroupExpandListener.onGroupExpand(posMetadata.position.groupPos);
                 }
+                
+                final int groupPos = posMetadata.position.groupPos;
+                final int groupFlatPos = posMetadata.position.flatListPos;
+                
+                smoothScrollToPosition(groupFlatPos + mAdapter.getChildrenCount(groupPos),
+                        groupFlatPos);
             }
 
             returnValue = true;
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index b4e2790..ea5841a 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -360,7 +360,8 @@
                 // Optimization: don't bother measuring children who are going to use
                 // leftover space. These views will get measured again down below if
                 // there is any leftover space.
-                mTotalLength += lp.topMargin + lp.bottomMargin;
+                final int totalLength = mTotalLength;
+                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
             } else {
                 int oldHeight = Integer.MIN_VALUE;
 
@@ -385,8 +386,9 @@
                 }
 
                 final int childHeight = child.getMeasuredHeight();
-                mTotalLength += childHeight + lp.topMargin +
-                       lp.bottomMargin + getNextLocationOffset(child);
+                final int totalLength = mTotalLength;
+                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
+                       lp.bottomMargin + getNextLocationOffset(child));
 
                 if (useLargestChild) {
                     largestChildHeight = Math.max(childHeight, largestChildHeight);
@@ -459,8 +461,10 @@
 
                 final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
                         child.getLayoutParams();
-                mTotalLength += largestChildHeight + lp.topMargin+ lp.bottomMargin +
-                        getNextLocationOffset(child);
+                // Account for negative margins
+                final int totalLength = mTotalLength;
+                mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
+                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
             }
         }
 
@@ -536,12 +540,14 @@
 
                 allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
 
-                mTotalLength += child.getMeasuredHeight() + lp.topMargin +
-                        lp.bottomMargin + getNextLocationOffset(child);
+                final int totalLength = mTotalLength;
+                mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
+                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
             }
 
             // Add in our padding
-            mTotalLength += mPaddingTop + mPaddingBottom;            
+            mTotalLength += mPaddingTop + mPaddingBottom;
+            // TODO: Should we recompute the heightSpec based on the new total length?
         } else {
             alternativeMaxWidth = Math.max(alternativeMaxWidth,
                                            weightedMaxWidth);
@@ -651,7 +657,8 @@
                 // Optimization: don't bother measuring children who are going to use
                 // leftover space. These views will get measured again down below if
                 // there is any leftover space.
-                mTotalLength += lp.leftMargin + lp.rightMargin;
+                final int totalLength = mTotalLength;
+                mTotalLength = Math.max(totalLength, totalLength + lp.leftMargin + lp.rightMargin);
 
                 // Baseline alignment requires to measure widgets to obtain the
                 // baseline offset (in particular for TextViews).
@@ -686,8 +693,9 @@
                 }
 
                 final int childWidth = child.getMeasuredWidth();
-                mTotalLength += childWidth + lp.leftMargin + lp.rightMargin +
-                        getNextLocationOffset(child);
+                final int totalLength = mTotalLength;
+                mTotalLength = Math.max(totalLength, totalLength + childWidth + lp.leftMargin +
+                        lp.rightMargin + getNextLocationOffset(child));
 
                 if (useLargestChild) {
                     largestChildWidth = Math.max(childWidth, largestChildWidth);
@@ -772,8 +780,9 @@
 
                 final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
                         child.getLayoutParams();
-                mTotalLength += largestChildWidth + lp.leftMargin + lp.rightMargin +
-                        getNextLocationOffset(child);
+                final int totalLength = mTotalLength;
+                mTotalLength = Math.max(totalLength, totalLength + largestChildWidth +
+                        lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
             }
         }
 
@@ -843,8 +852,9 @@
                     }
                 }
 
-                mTotalLength += child.getMeasuredWidth() + lp.leftMargin +
-                        lp.rightMargin + getNextLocationOffset(child);
+                final int totalLength = mTotalLength;
+                mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredWidth() +
+                        lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
 
                 boolean matchHeightLocally = heightMode != MeasureSpec.EXACTLY &&
                         lp.height == LayoutParams.MATCH_PARENT;
@@ -875,6 +885,7 @@
 
             // Add in our padding
             mTotalLength += mPaddingLeft + mPaddingRight;
+            // TODO: Should we update widthSize with the new total length?
 
             // Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP,
             // the most common case
diff --git a/core/java/com/android/internal/widget/WeightedLinearLayout.java b/core/java/com/android/internal/widget/WeightedLinearLayout.java
index b90204e..3d09f08 100644
--- a/core/java/com/android/internal/widget/WeightedLinearLayout.java
+++ b/core/java/com/android/internal/widget/WeightedLinearLayout.java
@@ -52,7 +52,8 @@
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
-        final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
+        final int screenWidth = metrics.widthPixels;
+        final boolean isPortrait = screenWidth < metrics.heightPixels;
 
         final int widthMode = getMode(widthMeasureSpec);
 
@@ -62,14 +63,13 @@
         int height = getMeasuredHeight();
         boolean measure = false;
 
-        final int widthSize = getSize(widthMeasureSpec);
         widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);
         heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, EXACTLY);
 
         final float widthWeight = isPortrait ? mMinorWeight : mMajorWeight;
         if (widthMode == AT_MOST && widthWeight > 0.0f) {
-            if (width < (widthSize * widthWeight)) {
-                widthMeasureSpec = MeasureSpec.makeMeasureSpec((int) (widthSize * widthWeight),
+            if (width < (screenWidth * widthWeight)) {
+                widthMeasureSpec = MeasureSpec.makeMeasureSpec((int) (screenWidth * widthWeight),
                         EXACTLY);
                 measure = true;
             }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1406b66..713e7258 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -679,6 +679,12 @@
         android:label="@string/permlab_setWallpaperHints"
         android:description="@string/permdesc_setWallpaperHints" />
 
+    <!-- Allows applications to set the system time -->
+    <permission android:name="android.permission.SET_TIME"
+        android:protectionLevel="signatureOrSystem"
+        android:label="@string/permlab_setTime"
+        android:description="@string/permdesc_setTime" />
+
     <!-- Allows applications to set the system time zone -->
     <permission android:name="android.permission.SET_TIME_ZONE"
         android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
diff --git a/core/res/res/drawable-hdpi/stat_sys_secure.png b/core/res/res/drawable-hdpi/stat_sys_secure.png
new file mode 100644
index 0000000..4bae258
--- /dev/null
+++ b/core/res/res/drawable-hdpi/stat_sys_secure.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_secure.png b/core/res/res/drawable-mdpi/stat_sys_secure.png
new file mode 100644
index 0000000..5f9ae69
--- /dev/null
+++ b/core/res/res/drawable-mdpi/stat_sys_secure.png
Binary files differ
diff --git a/core/res/res/layout/alert_dialog.xml b/core/res/res/layout/alert_dialog.xml
index 231e11c..7ae68f9 100644
--- a/core/res/res/layout/alert_dialog.xml
+++ b/core/res/res/layout/alert_dialog.xml
@@ -28,8 +28,8 @@
     android:paddingBottom="3dip"
     android:paddingLeft="3dip"
     android:paddingRight="1dip"
-    android:majorWeight="0.5"
-    android:minorWeight="0.8">
+    android:majorWeight="0.65"
+    android:minorWeight="0.9">
 
     <LinearLayout android:id="@+id/topPanel"
         android:layout_width="match_parent"
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index 3dbfa25..cdc15c2 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -107,6 +107,7 @@
          icons in the status bar that are not notifications. -->
     <string-array name="status_bar_icon_order">
         <item><xliff:g id="id">clock</xliff:g></item>
+        <item><xliff:g id="id">secure</xliff:g></item>
         <item><xliff:g id="id">alarm_clock</xliff:g></item>
         <item><xliff:g id="id">battery</xliff:g></item>
         <item><xliff:g id="id">phone_signal</xliff:g></item>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d1bfc68..4df570c 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1017,6 +1017,12 @@
         configuration, and installed applications.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_setTime">set time</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_setTime">Allows an application to change
+        the phone\'s clock time.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_setTimeZone">set time zone</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_setTimeZone">Allows an application to change
diff --git a/core/tests/coretests/src/android/content/SyncStorageEngineTest.java b/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
index 533338e..1505a7c 100644
--- a/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
+++ b/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
@@ -18,16 +18,23 @@
 
 import android.test.AndroidTestCase;
 import android.test.RenamingDelegatingContext;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.test.mock.MockContext;
 import android.test.mock.MockContentResolver;
 import android.accounts.Account;
+import android.os.Bundle;
+
+import java.util.List;
+import java.io.File;
 
 public class SyncStorageEngineTest extends AndroidTestCase {
 
     /**
      * Test that we handle the case of a history row being old enough to purge before the
      * correcponding sync is finished. This can happen if the clock changes while we are syncing.
+     *
      */
+    @SmallTest
     public void testPurgeActiveSync() throws Exception {
         final Account account = new Account("a@example.com", "example.type");
         final String authority = "testprovider";
@@ -41,7 +48,150 @@
         long historyId = engine.insertStartSyncEvent(
                 account, authority, time0, SyncStorageEngine.SOURCE_LOCAL);
         long time1 = time0 + SyncStorageEngine.MILLIS_IN_4WEEKS * 2;
-        engine.stopSyncEvent(historyId, time1 - time0, "yay", 0, 0);
+        engine.stopSyncEvent(historyId, new Bundle(), time1 - time0, "yay", 0, 0);
+    }
+
+    /**
+     * Test that we can create, remove and retrieve periodic syncs
+     */
+    @SmallTest
+    public void testPeriodics() throws Exception {
+        final Account account1 = new Account("a@example.com", "example.type");
+        final Account account2 = new Account("b@example.com", "example.type.2");
+        final String authority = "testprovider";
+        final Bundle extras1 = new Bundle();
+        extras1.putString("a", "1");
+        final Bundle extras2 = new Bundle();
+        extras2.putString("a", "2");
+        final int period1 = 200;
+        final int period2 = 1000;
+
+        PeriodicSync sync1 = new PeriodicSync(account1, authority, extras1, period1);
+        PeriodicSync sync2 = new PeriodicSync(account1, authority, extras2, period1);
+        PeriodicSync sync3 = new PeriodicSync(account1, authority, extras2, period2);
+        PeriodicSync sync4 = new PeriodicSync(account2, authority, extras2, period2);
+
+        MockContentResolver mockResolver = new MockContentResolver();
+
+        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
+                new TestContext(mockResolver, getContext()));
+
+        removePeriodicSyncs(engine, account1, authority);
+        removePeriodicSyncs(engine, account2, authority);
+
+        // this should add two distinct periodic syncs for account1 and one for account2
+        engine.addPeriodicSync(sync1.account, sync1.authority, sync1.extras, sync1.period);
+        engine.addPeriodicSync(sync2.account, sync2.authority, sync2.extras, sync2.period);
+        engine.addPeriodicSync(sync3.account, sync3.authority, sync3.extras, sync3.period);
+        engine.addPeriodicSync(sync4.account, sync4.authority, sync4.extras, sync4.period);
+
+        List<PeriodicSync> syncs = engine.getPeriodicSyncs(account1, authority);
+
+        assertEquals(2, syncs.size());
+
+        assertEquals(sync1, syncs.get(0));
+        assertEquals(sync3, syncs.get(1));
+
+        engine.removePeriodicSync(sync1.account, sync1.authority, sync1.extras);
+
+        syncs = engine.getPeriodicSyncs(account1, authority);
+        assertEquals(1, syncs.size());
+        assertEquals(sync3, syncs.get(0));
+
+        syncs = engine.getPeriodicSyncs(account2, authority);
+        assertEquals(1, syncs.size());
+        assertEquals(sync4, syncs.get(0));
+    }
+
+    private void removePeriodicSyncs(SyncStorageEngine engine, Account account, String authority) {
+        engine.setIsSyncable(account, authority, engine.getIsSyncable(account, authority));
+        List<PeriodicSync> syncs = engine.getPeriodicSyncs(account, authority);
+        for (PeriodicSync sync : syncs) {
+            engine.removePeriodicSync(sync.account, sync.authority, sync.extras);
+        }
+    }
+
+    @SmallTest
+    public void testAuthorityPersistence() throws Exception {
+        final Account account1 = new Account("a@example.com", "example.type");
+        final Account account2 = new Account("b@example.com", "example.type.2");
+        final String authority1 = "testprovider1";
+        final String authority2 = "testprovider2";
+        final Bundle extras1 = new Bundle();
+        extras1.putString("a", "1");
+        final Bundle extras2 = new Bundle();
+        extras2.putString("a", "2");
+        extras2.putLong("b", 2);
+        extras2.putInt("c", 1);
+        extras2.putBoolean("d", true);
+        extras2.putDouble("e", 1.2);
+        extras2.putFloat("f", 4.5f);
+        extras2.putParcelable("g", account1);
+        final int period1 = 200;
+        final int period2 = 1000;
+
+        PeriodicSync sync1 = new PeriodicSync(account1, authority1, extras1, period1);
+        PeriodicSync sync2 = new PeriodicSync(account1, authority1, extras2, period1);
+        PeriodicSync sync3 = new PeriodicSync(account1, authority2, extras1, period1);
+        PeriodicSync sync4 = new PeriodicSync(account1, authority2, extras2, period2);
+        PeriodicSync sync5 = new PeriodicSync(account2, authority1, extras1, period1);
+
+        MockContentResolver mockResolver = new MockContentResolver();
+
+        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
+                new TestContext(mockResolver, getContext()));
+
+        removePeriodicSyncs(engine, account1, authority1);
+        removePeriodicSyncs(engine, account2, authority1);
+        removePeriodicSyncs(engine, account1, authority2);
+        removePeriodicSyncs(engine, account2, authority2);
+
+        engine.setMasterSyncAutomatically(false);
+
+        engine.setIsSyncable(account1, authority1, 1);
+        engine.setSyncAutomatically(account1, authority1, true);
+
+        engine.setIsSyncable(account2, authority1, 1);
+        engine.setSyncAutomatically(account2, authority1, true);
+
+        engine.setIsSyncable(account1, authority2, 1);
+        engine.setSyncAutomatically(account1, authority2, false);
+
+        engine.setIsSyncable(account2, authority2, 0);
+        engine.setSyncAutomatically(account2, authority2, true);
+
+        engine.addPeriodicSync(sync1.account, sync1.authority, sync1.extras, sync1.period);
+        engine.addPeriodicSync(sync2.account, sync2.authority, sync2.extras, sync2.period);
+        engine.addPeriodicSync(sync3.account, sync3.authority, sync3.extras, sync3.period);
+        engine.addPeriodicSync(sync4.account, sync4.authority, sync4.extras, sync4.period);
+        engine.addPeriodicSync(sync5.account, sync5.authority, sync5.extras, sync5.period);
+
+        engine.writeAllState();
+        engine.clearAndReadState();
+
+        List<PeriodicSync> syncs = engine.getPeriodicSyncs(account1, authority1);
+        assertEquals(2, syncs.size());
+        assertEquals(sync1, syncs.get(0));
+        assertEquals(sync2, syncs.get(1));
+
+        syncs = engine.getPeriodicSyncs(account1, authority2);
+        assertEquals(2, syncs.size());
+        assertEquals(sync3, syncs.get(0));
+        assertEquals(sync4, syncs.get(1));
+
+        syncs = engine.getPeriodicSyncs(account2, authority1);
+        assertEquals(1, syncs.size());
+        assertEquals(sync5, syncs.get(0));
+
+        assertEquals(true, engine.getSyncAutomatically(account1, authority1));
+        assertEquals(true, engine.getSyncAutomatically(account2, authority1));
+        assertEquals(false, engine.getSyncAutomatically(account1, authority2));
+        assertEquals(true, engine.getSyncAutomatically(account2, authority2));
+
+        assertEquals(1, engine.getIsSyncable(account1, authority1));
+        assertEquals(1, engine.getIsSyncable(account2, authority1));
+        assertEquals(1, engine.getIsSyncable(account1, authority2));
+        assertEquals(0, engine.getIsSyncable(account2, authority2));
     }
 }
 
@@ -49,15 +199,26 @@
 
     ContentResolver mResolver;
 
+    private final Context mRealContext;
+
     public TestContext(ContentResolver resolver, Context realContext) {
         super(new RenamingDelegatingContext(new MockContext(), realContext, "test."));
+        mRealContext = realContext;
         mResolver = resolver;
     }
 
     @Override
+    public File getFilesDir() {
+        return mRealContext.getFilesDir();
+    }
+
+    @Override
     public void enforceCallingOrSelfPermission(String permission, String message) {
     }
 
+    @Override
+    public void sendBroadcast(Intent intent) {
+    }
 
     @Override
     public ContentResolver getContentResolver() {
diff --git a/libs/surfaceflinger/LayerBuffer.cpp b/libs/surfaceflinger/LayerBuffer.cpp
index 2735aa2..bd3113b 100644
--- a/libs/surfaceflinger/LayerBuffer.cpp
+++ b/libs/surfaceflinger/LayerBuffer.cpp
@@ -328,7 +328,8 @@
 
 LayerBuffer::BufferSource::BufferSource(LayerBuffer& layer,
         const ISurface::BufferHeap& buffers)
-    : Source(layer), mStatus(NO_ERROR), mBufferSize(0)
+    : Source(layer), mStatus(NO_ERROR), mBufferSize(0),
+      mUseEGLImageDirectly(true)
 {
     if (buffers.heap == NULL) {
         // this is allowed, but in this case, it is illegal to receive
@@ -466,25 +467,38 @@
 
 #if defined(EGL_ANDROID_image_native_buffer)
     if (mLayer.mFlags & DisplayHardware::DIRECT_TEXTURE) {
-        copybit_device_t* copybit = mLayer.mBlitEngine;
-        if (copybit && ourBuffer->supportsCopybit()) {
-            // create our EGLImageKHR the first time
-            err = initTempBuffer();
-            if (err == NO_ERROR) {
+        err = INVALID_OPERATION;
+        if (ourBuffer->supportsCopybit()) {
+            // First, try to use the buffer as an EGLImage directly
+            if (mUseEGLImageDirectly) {
                 // NOTE: Assume the buffer is allocated with the proper USAGE flags
-                const NativeBuffer& dst(mTempBuffer);
-                region_iterator clip(Region(Rect(dst.crop.r, dst.crop.b)));
-                copybit->set_parameter(copybit, COPYBIT_TRANSFORM, 0);
-                copybit->set_parameter(copybit, COPYBIT_PLANE_ALPHA, 0xFF);
-                copybit->set_parameter(copybit, COPYBIT_DITHER, COPYBIT_ENABLE);
-                err = copybit->stretch(copybit, &dst.img, &src.img,
-                        &dst.crop, &src.crop, &clip);
+                sp<GraphicBuffer> buffer = new  GraphicBuffer(
+                        src.img.w, src.img.h, src.img.format,
+                        GraphicBuffer::USAGE_HW_TEXTURE,
+                        src.img.w, src.img.handle, false);
+                err = mLayer.initializeEglImage(buffer, &mTexture);
                 if (err != NO_ERROR) {
-                    clearTempBufferImage();
+                    mUseEGLImageDirectly = false;
                 }
             }
-        } else {
-            err = INVALID_OPERATION;
+            copybit_device_t* copybit = mLayer.mBlitEngine;
+            if (copybit && err != NO_ERROR) {
+                // create our EGLImageKHR the first time
+                err = initTempBuffer();
+                if (err == NO_ERROR) {
+                    // NOTE: Assume the buffer is allocated with the proper USAGE flags
+                    const NativeBuffer& dst(mTempBuffer);
+                    region_iterator clip(Region(Rect(dst.crop.r, dst.crop.b)));
+                    copybit->set_parameter(copybit, COPYBIT_TRANSFORM, 0);
+                    copybit->set_parameter(copybit, COPYBIT_PLANE_ALPHA, 0xFF);
+                    copybit->set_parameter(copybit, COPYBIT_DITHER, COPYBIT_ENABLE);
+                    err = copybit->stretch(copybit, &dst.img, &src.img,
+                            &dst.crop, &src.crop, &clip);
+                    if (err != NO_ERROR) {
+                        clearTempBufferImage();
+                    }
+                }
+            }
         }
     }
 #endif
diff --git a/libs/surfaceflinger/LayerBuffer.h b/libs/surfaceflinger/LayerBuffer.h
index e03f92c..3257b76 100644
--- a/libs/surfaceflinger/LayerBuffer.h
+++ b/libs/surfaceflinger/LayerBuffer.h
@@ -145,6 +145,7 @@
         mutable LayerBase::Texture      mTexture;
         mutable NativeBuffer            mTempBuffer;
         mutable sp<GraphicBuffer>       mTempGraphicBuffer;
+        mutable bool                    mUseEGLImageDirectly;
     };
     
     class OverlaySource : public Source {
diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk
index d51ab30..b4c7212 100644
--- a/media/libmediaplayerservice/Android.mk
+++ b/media/libmediaplayerservice/Android.mk
@@ -54,7 +54,7 @@
 LOCAL_SHARED_LIBRARIES += libdl
 endif
 
-LOCAL_C_INCLUDES := external/tremor/Tremor                              \
+LOCAL_C_INCLUDES := external/tremolo/Tremolo                            \
 	$(JNI_H_INCLUDE)                                                \
 	$(call include-path-for, graphics corecg)                       \
 	$(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index b25ce67..6b880e8 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -49,7 +49,7 @@
 	$(JNI_H_INCLUDE) \
         $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \
         $(TOP)/external/opencore/android \
-        $(TOP)/external/tremor/Tremor
+        $(TOP)/external/tremolo/Tremolo
 
 LOCAL_SHARED_LIBRARIES := \
         libbinder         \
diff --git a/media/libstagefright/omx/tests/OMXHarness.cpp b/media/libstagefright/omx/tests/OMXHarness.cpp
index 51fcaf5..c05d90a 100644
--- a/media/libstagefright/omx/tests/OMXHarness.cpp
+++ b/media/libstagefright/omx/tests/OMXHarness.cpp
@@ -512,7 +512,9 @@
 
     sp<MediaExtractor> extractor = CreateExtractorFromURI(url);
 
-    CHECK(extractor != NULL);
+    if (extractor == NULL) {
+        return NULL;
+    }
 
     for (size_t i = 0; i < extractor->countTracks(); ++i) {
         sp<MetaData> meta = extractor->getTrackMetaData(i);
@@ -571,6 +573,10 @@
     sp<MediaSource> source = CreateSourceForMime(mime);
 
     sp<MediaSource> seekSource = CreateSourceForMime(mime);
+    if (source == NULL || seekSource == NULL) {
+        return UNKNOWN_ERROR;
+    }
+
     CHECK_EQ(seekSource->start(), OK);
 
     sp<MediaSource> codec = OMXCodec::Create(
diff --git a/opengl/libagl/egl.cpp b/opengl/libagl/egl.cpp
index 81864bd..b6e0aae 100644
--- a/opengl/libagl/egl.cpp
+++ b/opengl/libagl/egl.cpp
@@ -2092,7 +2092,20 @@
 
     if (native_buffer->common.version != sizeof(android_native_buffer_t))
         return setError(EGL_BAD_PARAMETER, EGL_NO_IMAGE_KHR);
-    
+
+    switch (native_buffer->format) {
+        case HAL_PIXEL_FORMAT_RGBA_8888:
+        case HAL_PIXEL_FORMAT_RGBX_8888:
+        case HAL_PIXEL_FORMAT_RGB_888:
+        case HAL_PIXEL_FORMAT_RGB_565:
+        case HAL_PIXEL_FORMAT_BGRA_8888:
+        case HAL_PIXEL_FORMAT_RGBA_5551:
+        case HAL_PIXEL_FORMAT_RGBA_4444:
+            break;
+        default:
+            return setError(EGL_BAD_PARAMETER, EGL_NO_IMAGE_KHR);
+    }
+
     native_buffer->common.incRef(&native_buffer->common);
     return (EGLImageKHR)native_buffer;
 }
diff --git a/opengl/libagl/texture.cpp b/opengl/libagl/texture.cpp
index a1a776f..fa25fa9 100644
--- a/opengl/libagl/texture.cpp
+++ b/opengl/libagl/texture.cpp
@@ -1628,6 +1628,11 @@
         return;
     }
 
+    if (image == EGL_NO_IMAGE_KHR) {
+        ogles_error(c, GL_INVALID_VALUE);
+        return;
+    }
+
     android_native_buffer_t* native_buffer = (android_native_buffer_t*)image;
     if (native_buffer->common.magic != ANDROID_NATIVE_BUFFER_MAGIC) {
         ogles_error(c, GL_INVALID_VALUE);
@@ -1652,4 +1657,26 @@
 
 void glEGLImageTargetRenderbufferStorageOES(GLenum target, GLeglImageOES image)
 {
+    ogles_context_t* c = ogles_context_t::get();
+    if (target != GL_RENDERBUFFER_OES) {
+        ogles_error(c, GL_INVALID_ENUM);
+        return;
+    }
+
+    if (image == EGL_NO_IMAGE_KHR) {
+        ogles_error(c, GL_INVALID_VALUE);
+        return;
+    }
+
+    android_native_buffer_t* native_buffer = (android_native_buffer_t*)image;
+    if (native_buffer->common.magic != ANDROID_NATIVE_BUFFER_MAGIC) {
+        ogles_error(c, GL_INVALID_VALUE);
+        return;
+    }
+    if (native_buffer->common.version != sizeof(android_native_buffer_t)) {
+        ogles_error(c, GL_INVALID_VALUE);
+        return;
+    }
+
+    // well, we're not supporting this extension anyways
 }
diff --git a/packages/TtsService/src/android/tts/TtsService.java b/packages/TtsService/src/android/tts/TtsService.java
index 1efa5a3..b7eea2e 100755
--- a/packages/TtsService/src/android/tts/TtsService.java
+++ b/packages/TtsService/src/android/tts/TtsService.java
@@ -802,8 +802,8 @@
                     }
                     if (synthAvailable) {
                         synthesizerLock.unlock();
+                        processSpeechQueue();
                     }
-                    processSpeechQueue();
                 }
             }
         }
@@ -882,8 +882,8 @@
                     }
                     if (synthAvailable) {
                         synthesizerLock.unlock();
+                        processSpeechQueue();
                     }
-                    processSpeechQueue();
                 }
             }
         }
diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java
index f330651..c28cf44 100644
--- a/services/java/com/android/server/AlarmManagerService.java
+++ b/services/java/com/android/server/AlarmManagerService.java
@@ -242,6 +242,14 @@
         setRepeating(type, bucketTime, interval, operation);
     }
 
+    public void setTime(long millis) {
+        mContext.enforceCallingOrSelfPermission(
+                "android.permission.SET_TIME",
+                "setTime");
+
+        SystemClock.setCurrentTimeMillis(millis);
+    }
+
     public void setTimeZone(String tz) {
         mContext.enforceCallingOrSelfPermission(
                 "android.permission.SET_TIME_ZONE",
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 6795bdd..2f845e1 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -258,8 +258,11 @@
 
                 // snapshot the pending-backup set and work on that
                 ArrayList<BackupRequest> queue = new ArrayList<BackupRequest>();
+                File oldJournal = mJournal;
                 synchronized (mQueueLock) {
-                    // Do we have any work to do?
+                    // Do we have any work to do?  Construct the work queue
+                    // then release the synchronization lock to actually run
+                    // the backup.
                     if (mPendingBackups.size() > 0) {
                         for (BackupRequest b: mPendingBackups.values()) {
                             queue.add(b);
@@ -268,20 +271,22 @@
                         mPendingBackups.clear();
 
                         // Start a new backup-queue journal file too
-                        File oldJournal = mJournal;
                         mJournal = null;
 
-                        // At this point, we have started a new journal file, and the old
-                        // file identity is being passed to the backup processing thread.
-                        // When it completes successfully, that old journal file will be
-                        // deleted.  If we crash prior to that, the old journal is parsed
-                        // at next boot and the journaled requests fulfilled.
-                        (new PerformBackupTask(transport, queue, oldJournal)).run();
-                    } else {
-                        Log.v(TAG, "Backup requested but nothing pending");
-                        mWakelock.release();
                     }
                 }
+
+                if (queue.size() > 0) {
+                    // At this point, we have started a new journal file, and the old
+                    // file identity is being passed to the backup processing thread.
+                    // When it completes successfully, that old journal file will be
+                    // deleted.  If we crash prior to that, the old journal is parsed
+                    // at next boot and the journaled requests fulfilled.
+                    (new PerformBackupTask(transport, queue, oldJournal)).run();
+                } else {
+                    Log.v(TAG, "Backup requested but nothing pending");
+                    mWakelock.release();
+                }
                 break;
             }
 
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index e14a973..1e7dd99 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -212,11 +212,13 @@
     private Sensor mLightSensor;
     private boolean mLightSensorEnabled;
     private float mLightSensorValue = -1;
+    private int mHighestLightSensorValue = -1;
     private float mLightSensorPendingValue = -1;
     private int mLightSensorScreenBrightness = -1;
     private int mLightSensorButtonBrightness = -1;
     private int mLightSensorKeyboardBrightness = -1;
     private boolean mDimScreen = true;
+    private boolean mIsDocked = false;
     private long mNextTimeout;
     private volatile int mPokey = 0;
     private volatile boolean mPokeAwakeOnSet = false;
@@ -363,6 +365,15 @@
         }
     }
 
+    private final class DockReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
+                    Intent.EXTRA_DOCK_STATE_UNDOCKED);
+            dockStateChanged(state);
+        }
+    }
+
     /**
      * Set the setting that determines whether the device stays on when plugged in.
      * The argument is a bit string, with each bit specifying a power source that,
@@ -527,6 +538,9 @@
         filter = new IntentFilter();
         filter.addAction(Intent.ACTION_BOOT_COMPLETED);
         mContext.registerReceiver(new BootCompletedReceiver(), filter);
+        filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_DOCK_EVENT);
+        mContext.registerReceiver(new DockReceiver(), filter);
 
         // Listen for secure settings changes
         mContext.getContentResolver().registerContentObserver(
@@ -1389,6 +1403,8 @@
                     // clear current value so we will update based on the new conditions
                     // when the sensor is reenabled.
                     mLightSensorValue = -1;
+                    // reset our highest light sensor value when the screen turns off
+                    mHighestLightSensorValue = -1;
                 }
             }
         }
@@ -2059,15 +2075,40 @@
         }
     };
 
+    private void dockStateChanged(int state) {
+        synchronized (mLocks) {
+            mIsDocked = (state != Intent.EXTRA_DOCK_STATE_UNDOCKED);
+            if (mIsDocked) {
+                mHighestLightSensorValue = -1;
+            }
+            if ((mPowerState & SCREEN_ON_BIT) != 0) {
+                // force lights recalculation
+                int value = (int)mLightSensorValue;
+                mLightSensorValue = -1;
+                lightSensorChangedLocked(value);
+            }
+        }
+    }
+
     private void lightSensorChangedLocked(int value) {
         if (mDebugLightSensor) {
             Log.d(TAG, "lightSensorChangedLocked " + value);
         }
 
+        // do not allow light sensor value to decrease
+        if (mHighestLightSensorValue < value) {
+            mHighestLightSensorValue = value;
+        }
+
         if (mLightSensorValue != value) {
             mLightSensorValue = value;
             if ((mPowerState & BATTERY_LOW_BIT) == 0) {
-                int lcdValue = getAutoBrightnessValue(value, mLcdBacklightValues);
+                // use maximum light sensor value seen since screen went on for LCD to avoid flicker
+                // we only do this if we are undocked, since lighting should be stable when
+                // stationary in a dock.
+                int lcdValue = getAutoBrightnessValue(
+                        (mIsDocked ? value : mHighestLightSensorValue),
+                        mLcdBacklightValues);
                 int buttonValue = getAutoBrightnessValue(value, mButtonBacklightValues);
                 int keyboardValue;
                 if (mKeyboardVisible) {
diff --git a/tests/AndroidTests/Android.mk b/tests/AndroidTests/Android.mk
index bff8fba..0d29c35 100644
--- a/tests/AndroidTests/Android.mk
+++ b/tests/AndroidTests/Android.mk
@@ -5,8 +5,6 @@
 
 LOCAL_JAVA_LIBRARIES := framework-tests android.test.runner services
 
-LOCAL_STATIC_JAVA_LIBRARIES := gsf-client
-
 # Resource unit tests use a private locale
 LOCAL_AAPT_FLAGS = -c xx_YY -c cs -c 160dpi -c 32dpi -c 240dpi
 
diff --git a/tests/CoreTests/android/content/SyncQueueTest.java b/tests/CoreTests/android/content/SyncQueueTest.java
index 5f4ab78..1da59d1 100644
--- a/tests/CoreTests/android/content/SyncQueueTest.java
+++ b/tests/CoreTests/android/content/SyncQueueTest.java
@@ -66,22 +66,22 @@
 
         long now = SystemClock.elapsedRealtime() + 200;
 
-        assertEquals(op6, mSyncQueue.nextReadyToRun(now));
+        assertEquals(op6, mSyncQueue.nextReadyToRun(now).first);
         mSyncQueue.remove(op6);
 
-        assertEquals(op1, mSyncQueue.nextReadyToRun(now));
+        assertEquals(op1, mSyncQueue.nextReadyToRun(now).first);
         mSyncQueue.remove(op1);
 
-        assertEquals(op4, mSyncQueue.nextReadyToRun(now));
+        assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
         mSyncQueue.remove(op4);
 
-        assertEquals(op5, mSyncQueue.nextReadyToRun(now));
+        assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
         mSyncQueue.remove(op5);
 
-        assertEquals(op2, mSyncQueue.nextReadyToRun(now));
+        assertEquals(op2, mSyncQueue.nextReadyToRun(now).first);
         mSyncQueue.remove(op2);
 
-        assertEquals(op3, mSyncQueue.nextReadyToRun(now));
+        assertEquals(op3, mSyncQueue.nextReadyToRun(now).first);
         mSyncQueue.remove(op3);
     }
 
@@ -109,32 +109,32 @@
 
         long now = SystemClock.elapsedRealtime() + 200;
 
-        assertEquals(op6, mSyncQueue.nextReadyToRun(now));
+        assertEquals(op6, mSyncQueue.nextReadyToRun(now).first);
         mSyncQueue.remove(op6);
 
-        assertEquals(op1, mSyncQueue.nextReadyToRun(now));
+        assertEquals(op1, mSyncQueue.nextReadyToRun(now).first);
         mSyncQueue.remove(op1);
 
         mSettings.setBackoff(ACCOUNT2,  AUTHORITY3, now + 200, 5);
-        assertEquals(op5, mSyncQueue.nextReadyToRun(now));
+        assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
 
         mSettings.setBackoff(ACCOUNT2,  AUTHORITY3, SyncStorageEngine.NOT_IN_BACKOFF_MODE, 0);
-        assertEquals(op4, mSyncQueue.nextReadyToRun(now));
+        assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
 
         mSettings.setDelayUntilTime(ACCOUNT2,  AUTHORITY3, now + 200);
-        assertEquals(op5, mSyncQueue.nextReadyToRun(now));
+        assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
 
         mSettings.setDelayUntilTime(ACCOUNT2,  AUTHORITY3, 0);
-        assertEquals(op4, mSyncQueue.nextReadyToRun(now));
+        assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
         mSyncQueue.remove(op4);
 
-        assertEquals(op5, mSyncQueue.nextReadyToRun(now));
+        assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
         mSyncQueue.remove(op5);
 
-        assertEquals(op2, mSyncQueue.nextReadyToRun(now));
+        assertEquals(op2, mSyncQueue.nextReadyToRun(now).first);
         mSyncQueue.remove(op2);
 
-        assertEquals(op3, mSyncQueue.nextReadyToRun(now));
+        assertEquals(op3, mSyncQueue.nextReadyToRun(now).first);
         mSyncQueue.remove(op3);
     }