Merge "Turn the debugging back on."
diff --git a/api/current.xml b/api/current.xml
index 527ffe3..03e20fa4 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -40253,6 +40253,16 @@
  visibility="public"
 >
 </field>
+<field name="delayUntil"
+ type="long"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="fullSyncRequested"
  type="boolean"
  transient="false"
@@ -55200,6 +55210,88 @@
 >
 </field>
 </class>
+<class name="GestureUtilities"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="computeOrientedBoundingBox"
+ return="android.gesture.OrientedBoundingBox"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="originalPoints" type="java.util.ArrayList&lt;android.gesture.GesturePoint&gt;">
+</parameter>
+</method>
+<method name="computeOrientedBoundingBox"
+ return="android.gesture.OrientedBoundingBox"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="originalPoints" type="float[]">
+</parameter>
+</method>
+<method name="spatialSampling"
+ return="float[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="gesture" type="android.gesture.Gesture">
+</parameter>
+<parameter name="bitmapSize" type="int">
+</parameter>
+</method>
+<method name="spatialSampling"
+ return="float[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="gesture" type="android.gesture.Gesture">
+</parameter>
+<parameter name="bitmapSize" type="int">
+</parameter>
+<parameter name="keepAspectRatio" type="boolean">
+</parameter>
+</method>
+<method name="temporalSampling"
+ return="float[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="stroke" type="android.gesture.GestureStroke">
+</parameter>
+<parameter name="numPoints" type="int">
+</parameter>
+</method>
+</class>
 <class name="OrientedBoundingBox"
  extends="java.lang.Object"
  abstract="false"
@@ -130602,6 +130694,17 @@
  visibility="public"
 >
 </field>
+<field name="ACTION_DEVICE_INFO_SETTINGS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.settings.DEVICE_INFO_SETTINGS&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="ACTION_DISPLAY_SETTINGS"
  type="java.lang.String"
  transient="false"
@@ -134065,6 +134168,320 @@
 </package>
 <package name="android.speech"
 >
+<interface name="RecognitionListener"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onBeginningOfSpeech"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onBufferReceived"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="buffer" type="byte[]">
+</parameter>
+</method>
+<method name="onEndOfSpeech"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onError"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="error" type="int">
+</parameter>
+</method>
+<method name="onInit"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onPartialResults"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="partialResults" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="onReadyForSpeech"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="params" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="onResults"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="results" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="onRmsChanged"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="rmsdB" type="float">
+</parameter>
+</method>
+</interface>
+<class name="RecognitionManager"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="cancel"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="createRecognitionManager"
+ return="android.speech.RecognitionManager"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="listener" type="android.speech.RecognitionListener">
+</parameter>
+<parameter name="recognizerIntent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="destroy"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isRecognitionAvailable"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</method>
+<method name="startListening"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="recognizerIntent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="stopListening"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<field name="AUDIO_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CLIENT_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="5"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="MANAGER_NOT_INITIALIZED_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="9"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="NETWORK_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="NETWORK_TIMEOUT_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="NO_MATCH_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="7"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="RECOGNITION_RESULTS_STRING_ARRAY"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;recognition_results_string_array&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SERVER_BUSY_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="8"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SERVER_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SPEECH_TIMEOUT_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="6"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
 <class name="RecognizerIntent"
  extends="java.lang.Object"
  abstract="false"
@@ -134084,6 +134501,17 @@
  visibility="public"
 >
 </field>
+<field name="ACTION_VOICE_SEARCH_SETTINGS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.speech.action.VOICE_SEARCH_SETTINGS&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="ACTION_WEB_SEARCH"
  type="java.lang.String"
  transient="false"
@@ -134172,6 +134600,39 @@
  visibility="public"
 >
 </field>
+<field name="EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.speech.extras.SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.speech.extras.SPEECH_INPUT_MINIMUM_LENGTH_MILLIS&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.speech.extras.SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="LANGUAGE_MODEL_FREE_FORM"
  type="java.lang.String"
  transient="false"
diff --git a/cmds/stagefright/audioloop.cpp b/cmds/stagefright/audioloop.cpp
index 3788e73..8733662 100644
--- a/cmds/stagefright/audioloop.cpp
+++ b/cmds/stagefright/audioloop.cpp
@@ -1,7 +1,10 @@
 #include "SineSource.h"
 
 #include <binder/ProcessState.h>
+#include <media/mediarecorder.h>
+#include <media/stagefright/AMRWriter.h>
 #include <media/stagefright/AudioPlayer.h>
+#include <media/stagefright/AudioSource.h>
 #include <media/stagefright/MediaDebug.h>
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/MetaData.h>
@@ -21,7 +24,16 @@
     OMXClient client;
     CHECK_EQ(client.connect(), OK);
 
+#if 0
     sp<MediaSource> source = new SineSource(kSampleRate, kNumChannels);
+#else
+    sp<MediaSource> source = new AudioSource(
+            AUDIO_SOURCE_DEFAULT,
+            kSampleRate,
+            kNumChannels == 1
+                ? AudioSystem::CHANNEL_IN_MONO
+                : AudioSystem::CHANNEL_IN_STEREO);
+#endif
 
     sp<MetaData> meta = new MetaData;
 
@@ -43,12 +55,19 @@
             meta, true /* createEncoder */,
             source);
 
+#if 1
+    sp<AMRWriter> writer = new AMRWriter("/sdcard/out.amr");
+    writer->addSource(encoder);
+    writer->start();
+    sleep(10);
+    writer->stop();
+#else
     sp<MediaSource> decoder = OMXCodec::Create(
             client.interface(),
             meta, false /* createEncoder */,
             encoder);
 
-#if 1
+#if 0
     AudioPlayer *player = new AudioPlayer(NULL);
     player->setSource(decoder);
 
@@ -60,7 +79,7 @@
 
     delete player;
     player = NULL;
-#else
+#elif 0
     CHECK_EQ(decoder->start(), OK);
 
     MediaBuffer *buffer;
@@ -76,6 +95,7 @@
 
     CHECK_EQ(decoder->stop(), OK);
 #endif
+#endif
 
     return 0;
 }
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index ee26d3c..e3ccd00 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -506,7 +506,7 @@
                     + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null);
         }
     }
-    
+
     public void removeAccount(IAccountManagerResponse response, Account account) {
         checkManageAccountsPermission();
         long identityToken = clearCallingIdentity();
@@ -1660,9 +1660,16 @@
                 }
             }
         } else {
+            Account[] accounts = getAccounts(null /* type */);
+            fout.println("Accounts: " + accounts.length);
+            for (Account account : accounts) {
+                fout.println("  " + account);
+            }
+
+            fout.println();
             synchronized (mSessions) {
                 final long now = SystemClock.elapsedRealtime();
-                fout.println("AccountManagerService: " + mSessions.size() + " sessions");
+                fout.println("Active Sessions: " + mSessions.size());
                 for (Session session : mSessions.values()) {
                     fout.println("  " + session.toDebugString(now));
                 }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index d76b616..321ba5c 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1278,6 +1278,17 @@
 
     /**
      * Use with {@link #getSystemService} to retrieve a {@link
+     * android.net.NetworkManagementService} for handling management of
+     * system network services
+     *
+     * @hide
+     * @see #getSystemService
+     * @see android.net.NetworkManagementService
+     */
+    public static final String NETWORKMANAGEMENT_SERVICE = "network_management";
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a {@link
      * android.net.wifi.WifiManager} for handling management of
      * Wi-Fi access.
      *
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index a9c61dc..c9077bc 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -16,8 +16,6 @@
 
 package android.content;
 
-import com.google.android.collect.Maps;
-
 import com.android.internal.R;
 import com.android.internal.util.ArrayUtils;
 
@@ -29,7 +27,6 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.RegisteredServicesCache;
@@ -45,15 +42,14 @@
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.provider.Settings;
 import android.text.format.DateUtils;
 import android.text.format.Time;
-import android.util.Config;
 import android.util.EventLog;
 import android.util.Log;
+import android.util.Pair;
 
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
@@ -65,12 +61,8 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
-import java.util.PriorityQueue;
 import java.util.Random;
 import java.util.Collection;
 import java.util.concurrent.CountDownLatch;
@@ -78,7 +70,7 @@
 /**
  * @hide
  */
-class SyncManager implements OnAccountsUpdateListener {
+public class SyncManager implements OnAccountsUpdateListener {
     private static final String TAG = "SyncManager";
 
     // used during dumping of the Sync history
@@ -144,9 +136,6 @@
 
     private Context mContext;
 
-    private String mStatusText = "";
-    private long mHeartbeatTime = 0;
-
     private volatile Account[] mAccounts = null;
 
     volatile private PowerManager.WakeLock mSyncWakeLock;
@@ -156,12 +145,9 @@
 
     private final NotificationManager mNotificationMgr;
     private AlarmManager mAlarmService = null;
-    private HandlerThread mSyncThread;
-
-    private volatile IPackageManager mPackageManager;
 
     private final SyncStorageEngine mSyncStorageEngine;
-    private final SyncQueue mSyncQueue;
+    public final SyncQueue mSyncQueue;
 
     private ActiveSyncContext mActiveSyncContext = null;
 
@@ -312,8 +298,6 @@
 
     private static final String SYNCMANAGER_PREFS_FILENAME = "/data/system/syncmanager.prefs";
 
-    private final boolean mFactoryTest;
-
     private volatile boolean mBootCompleted = false;
 
     private ConnectivityManager getConnectivityManager() {
@@ -327,8 +311,6 @@
     }
 
     public SyncManager(Context context, boolean factoryTest) {
-        mFactoryTest = factoryTest;
-
         // Initialize the SyncStorageEngine first, before registering observers
         // and creating threads and so on; it may fail if the disk is full.
         SyncStorageEngine.init(context);
@@ -337,11 +319,10 @@
 
         mContext = context;
 
-        mSyncThread = new HandlerThread("SyncHandlerThread", Process.THREAD_PRIORITY_BACKGROUND);
-        mSyncThread.start();
-        mSyncHandler = new SyncHandler(mSyncThread.getLooper());
-
-        mPackageManager = null;
+        HandlerThread syncThread = new HandlerThread("SyncHandlerThread",
+                Process.THREAD_PRIORITY_BACKGROUND);
+        syncThread.start();
+        mSyncHandler = new SyncHandler(syncThread.getLooper());
 
         mSyncAdapters = new SyncAdaptersCache(mContext);
 
@@ -635,16 +616,12 @@
             if (isLoggable) {
                 Log.v(TAG, "not syncing because sync is disabled");
             }
-            setStatusText("Sync is disabled.");
             return;
         }
 
         final boolean backgroundDataUsageAllowed = !mBootCompleted ||
                 getConnectivityManager().getBackgroundDataSetting();
 
-        if (!mDataConnectionIsConnected) setStatusText("No data connection");
-        if (mStorageIsLow) setStatusText("Memory low");
-
         if (extras == null) extras = new Bundle();
 
         Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
@@ -670,7 +647,6 @@
                 if (isLoggable) {
                     Log.v(TAG, "scheduleSync: no accounts configured, dropping");
                 }
-                setStatusText("No accounts are configured.");
                 return;
             }
         }
@@ -759,10 +735,6 @@
         }
     }
 
-    private void setStatusText(String message) {
-        mStatusText = message;
-    }
-
     public void scheduleLocalSync(Account account, String authority) {
         final Bundle extras = new Bundle();
         extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
@@ -770,17 +742,6 @@
                 false /* onlyThoseWithUnkownSyncableState */);
     }
 
-    private IPackageManager getPackageManager() {
-        // Don't bother synchronizing on this. The worst that can happen is that two threads
-        // can try to get the package manager at the same time but only one result gets
-        // used. Since there is only one package manager in the system this doesn't matter.
-        if (mPackageManager == null) {
-            IBinder b = ServiceManager.getService("package");
-            mPackageManager = IPackageManager.Stub.asInterface(b);
-        }
-        return mPackageManager;
-    }
-
     public SyncAdapterType[] getSyncAdapterTypes() {
         final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> serviceInfos =
                 mSyncAdapters.getAllServices();
@@ -793,11 +754,6 @@
         return types;
     }
 
-    public void updateHeartbeatTime() {
-        mHeartbeatTime = SystemClock.elapsedRealtime();
-        mSyncStorageEngine.reportActiveChange();
-    }
-
     private void sendSyncAlarmMessage() {
         if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_ALARM");
         mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_SYNC_ALARM);
@@ -840,22 +796,24 @@
         }
     }
 
-    private void rescheduleImmediately(SyncOperation syncOperation) {
-        SyncOperation rescheduledSyncOperation = new SyncOperation(syncOperation);
-        rescheduledSyncOperation.setDelay(0);
-        scheduleSyncOperation(rescheduledSyncOperation);
+    private void clearBackoffSetting(SyncOperation op) {
+        mSyncStorageEngine.setBackoff(op.account, op.authority,
+                SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
     }
 
-    private long rescheduleWithDelay(SyncOperation syncOperation) {
-        long newDelayInMs;
+    private void increaseBackoffSetting(SyncOperation op) {
+        final long now = SystemClock.elapsedRealtime();
 
-        if (syncOperation.delay <= 0) {
+        final Pair<Long, Long> previousSettings =
+                mSyncStorageEngine.getBackoff(op.account, op.authority);
+        long newDelayInMs;
+        if (previousSettings == null || previousSettings.second <= 0) {
             // The initial delay is the jitterized INITIAL_SYNC_RETRY_TIME_IN_MS
             newDelayInMs = jitterize(INITIAL_SYNC_RETRY_TIME_IN_MS,
                     (long)(INITIAL_SYNC_RETRY_TIME_IN_MS * 1.1));
         } else {
             // Subsequent delays are the double of the previous delay
-            newDelayInMs = syncOperation.delay * 2;
+            newDelayInMs = previousSettings.second * 2;
         }
 
         // Cap the delay
@@ -866,10 +824,20 @@
             newDelayInMs = maxSyncRetryTimeInSeconds * 1000;
         }
 
-        SyncOperation rescheduledSyncOperation = new SyncOperation(syncOperation);
-        rescheduledSyncOperation.setDelay(newDelayInMs);
-        scheduleSyncOperation(rescheduledSyncOperation);
-        return newDelayInMs;
+        mSyncStorageEngine.setBackoff(op.account, op.authority,
+                now + newDelayInMs, newDelayInMs);
+    }
+
+    private void setDelayUntilTime(SyncOperation op, long delayUntilSeconds) {
+        final long delayUntil = delayUntilSeconds * 1000;
+        final long absoluteNow = System.currentTimeMillis();
+        long newDelayUntilTime;
+        if (delayUntil > absoluteNow) {
+            newDelayUntilTime = SystemClock.elapsedRealtime() + (delayUntil - absoluteNow);
+        } else {
+            newDelayUntilTime = 0;
+        }
+        mSyncStorageEngine.setDelayUntilTime(op.account, op.authority, newDelayUntilTime);
     }
 
     /**
@@ -905,28 +873,26 @@
     public void scheduleSyncOperation(SyncOperation syncOperation) {
         // If this operation is expedited and there is a sync in progress then
         // reschedule the current operation and send a cancel for it.
-        final boolean expedited = syncOperation.delay < 0;
         final ActiveSyncContext activeSyncContext = mActiveSyncContext;
-        if (expedited && activeSyncContext != null) {
-            final boolean activeIsExpedited = activeSyncContext.mSyncOperation.delay < 0;
+        if (syncOperation.expedited && activeSyncContext != null) {
             final boolean hasSameKey =
                     activeSyncContext.mSyncOperation.key.equals(syncOperation.key);
             // This request is expedited and there is a sync in progress.
             // Interrupt the current sync only if it is not expedited and if it has a different
             // key than the one we are scheduling.
-            if (!activeIsExpedited && !hasSameKey) {
-                rescheduleImmediately(activeSyncContext.mSyncOperation);
+            if (!activeSyncContext.mSyncOperation.expedited && !hasSameKey) {
+                scheduleSyncOperation(new SyncOperation(activeSyncContext.mSyncOperation));
                 sendSyncFinishedOrCanceledMessage(activeSyncContext,
                         null /* no result since this is a cancel */);
             }
         }
 
-        boolean operationEnqueued;
+        boolean queueChanged;
         synchronized (mSyncQueue) {
-            operationEnqueued = mSyncQueue.add(syncOperation);
+            queueChanged = mSyncQueue.add(syncOperation);
         }
 
-        if (operationEnqueued) {
+        if (queueChanged) {
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "scheduleSyncOperation: enqueued " + syncOperation);
             }
@@ -945,16 +911,17 @@
      * @param authority limit the removals to operations with this authority, if non-null
      */
     public void clearScheduledSyncOperations(Account account, String authority) {
+        mSyncStorageEngine.setBackoff(account, authority,
+                SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
         synchronized (mSyncQueue) {
             mSyncQueue.clear(account, authority);
         }
     }
 
-    void maybeRescheduleSync(SyncResult syncResult, SyncOperation previousSyncOperation) {
+    void maybeRescheduleSync(SyncResult syncResult, SyncOperation operation) {
         boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
         if (isLoggable) {
-            Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", "
-                    + previousSyncOperation);
+            Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", " + operation);
         }
 
         // If this sync aborted because the internal sync loop retried too many times then
@@ -963,119 +930,33 @@
         // If this was a two-way sync then retry soft errors with an exponential backoff.
         // If this was an upward sync then schedule a two-way sync immediately.
         // Otherwise do not reschedule.
-        if (syncResult.tooManyRetries) {
+        if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)) {
+            Log.d(TAG, "not retrying sync operation because it is a manual sync: "
+                    + operation);
+        } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)) {
+            final SyncOperation newSyncOperation = new SyncOperation(operation);
+            newSyncOperation.extras.remove(ContentResolver.SYNC_EXTRAS_UPLOAD);
+            Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
+                    + "encountered an error: " + operation);
+            scheduleSyncOperation(newSyncOperation);
+        } else if (syncResult.tooManyRetries) {
             Log.d(TAG, "not retrying sync operation because it retried too many times: "
-                    + previousSyncOperation);
+                    + operation);
         } else if (syncResult.madeSomeProgress()) {
             if (isLoggable) {
-                Log.d(TAG, "retrying sync operation immediately because "
-                        + "even though it had an error it achieved some success");
+                Log.d(TAG, "retrying sync operation because even though it had an error "
+                        + "it achieved some success");
             }
-            rescheduleImmediately(previousSyncOperation);
-        } else if (previousSyncOperation.extras.getBoolean(
-                ContentResolver.SYNC_EXTRAS_UPLOAD, false)) {
-            final SyncOperation newSyncOperation = new SyncOperation(previousSyncOperation);
-            newSyncOperation.extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
-            newSyncOperation.setDelay(0);
-            if (Config.LOGD) {
-                Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
-                        + "encountered an error: " + previousSyncOperation);
-            }
-            scheduleSyncOperation(newSyncOperation);
+            scheduleSyncOperation(new SyncOperation(operation));
         } else if (syncResult.hasSoftError()) {
-            long delay = rescheduleWithDelay(previousSyncOperation);
-            if (delay >= 0) {
-                if (isLoggable) {
-                    Log.d(TAG, "retrying sync operation in " + delay + " ms because "
-                            + "it encountered a soft error: " + previousSyncOperation);
-                }
+            if (isLoggable) {
+                Log.d(TAG, "retrying sync operation because it encountered a soft error: "
+                        + operation);
             }
+            scheduleSyncOperation(new SyncOperation(operation));
         } else {
-            if (Config.LOGD) {
-                Log.d(TAG, "not retrying sync operation because the error is a hard error: "
-                        + previousSyncOperation);
-            }
-        }
-    }
-
-    /**
-     * Value type that represents a sync operation.
-     */
-    static class SyncOperation implements Comparable {
-        final Account account;
-        int syncSource;
-        String authority;
-        Bundle extras;
-        final String key;
-        long earliestRunTime;
-        long delay;
-        SyncStorageEngine.PendingOperation pendingOperation;
-
-        SyncOperation(Account account, int source, String authority, Bundle extras, long delay) {
-            this.account = account;
-            this.syncSource = source;
-            this.authority = authority;
-            this.extras = new Bundle(extras);
-            this.setDelay(delay);
-            this.key = toKey();
-        }
-
-        SyncOperation(SyncOperation other) {
-            this.account = other.account;
-            this.syncSource = other.syncSource;
-            this.authority = other.authority;
-            this.extras = new Bundle(other.extras);
-            this.delay = other.delay;
-            this.earliestRunTime = other.earliestRunTime;
-            this.key = toKey();
-        }
-
-        public void setDelay(long delay) {
-            this.delay = delay;
-            if (delay >= 0) {
-                this.earliestRunTime = SystemClock.elapsedRealtime() + delay;
-            } else {
-                this.earliestRunTime = 0;
-            }
-        }
-
-        public String toString() {
-            StringBuilder sb = new StringBuilder();
-            sb.append("authority: ").append(authority);
-            sb.append(" account: ").append(account);
-            sb.append(" extras: ");
-            extrasToStringBuilder(extras, sb);
-            sb.append(" syncSource: ").append(syncSource);
-            sb.append(" when: ").append(earliestRunTime);
-            sb.append(" delay: ").append(delay);
-            sb.append(" key: {").append(key).append("}");
-            if (pendingOperation != null) sb.append(" pendingOperation: ").append(pendingOperation);
-            return sb.toString();
-        }
-
-        private String toKey() {
-            StringBuilder sb = new StringBuilder();
-            sb.append("authority: ").append(authority);
-            sb.append(" account: ").append(account);
-            sb.append(" extras: ");
-            extrasToStringBuilder(extras, sb);
-            return sb.toString();
-        }
-
-        private static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
-            sb.append("[");
-            for (String key : bundle.keySet()) {
-                sb.append(key).append("=").append(bundle.get(key)).append(" ");
-            }
-            sb.append("]");
-        }
-
-        public int compareTo(Object o) {
-            SyncOperation other = (SyncOperation)o;
-            if (earliestRunTime == other.earliestRunTime) {
-                return 0;
-            }
-            return (earliestRunTime < other.earliestRunTime) ? -1 : 1;
+            Log.d(TAG, "not retrying sync operation because the error is a hard error: "
+                    + operation);
         }
     }
 
@@ -1100,10 +981,7 @@
         }
 
         public void sendHeartbeat() {
-            // ignore this call if it corresponds to an old sync session
-            if (mActiveSyncContext == this) {
-                SyncManager.this.updateHeartbeatTime();
-            }
+            // heartbeats are no longer used
         }
 
         public void onFinished(SyncResult result) {
@@ -1255,7 +1133,8 @@
                 pw.print("  #"); pw.print(i); pw.print(": account=");
                 pw.print(op.account.name); pw.print(":");
                 pw.print(op.account.type); pw.print(" authority=");
-                pw.println(op.authority);
+                pw.print(op.authority); pw.print(" expedited=");
+                pw.println(op.expedited);
                 if (op.extras != null && op.extras.size() > 0) {
                     sb.setLength(0);
                     SyncOperation.extrasToStringBuilder(op.extras, sb);
@@ -1295,6 +1174,24 @@
                         }
                         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);
@@ -1568,11 +1465,9 @@
                         }
                         SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload)msg.obj;
                         if (mActiveSyncContext != payload.activeSyncContext) {
-                            if (Config.LOGD) {
-                                Log.d(TAG, "handleSyncHandlerMessage: sync context doesn't match, "
-                                        + "dropping: mActiveSyncContext " + mActiveSyncContext
-                                        + " != " + payload.activeSyncContext);
-                            }
+                            Log.d(TAG, "handleSyncHandlerMessage: sync context doesn't match, "
+                                    + "dropping: mActiveSyncContext " + mActiveSyncContext
+                                    + " != " + payload.activeSyncContext);
                             return;
                         }
                         runSyncFinishedOrCanceled(payload.syncResult);
@@ -1685,14 +1580,12 @@
             if (now > activeSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC) {
                 SyncOperation nextSyncOperation;
                 synchronized (mSyncQueue) {
-                    nextSyncOperation = mSyncQueue.head();
+                    nextSyncOperation = mSyncQueue.nextReadyToRun(now);
                 }
-                if (nextSyncOperation != null && nextSyncOperation.earliestRunTime <= now) {
-                    if (Config.LOGD) {
-                        Log.d(TAG, "canceling and rescheduling sync because it ran too long: "
-                                + activeSyncContext.mSyncOperation);
-                    }
-                    rescheduleImmediately(activeSyncContext.mSyncOperation);
+                if (nextSyncOperation != null) {
+                    Log.d(TAG, "canceling and rescheduling sync because it ran too long: "
+                            + activeSyncContext.mSyncOperation);
+                    scheduleSyncOperation(new SyncOperation(activeSyncContext.mSyncOperation));
                     sendSyncFinishedOrCanceledMessage(activeSyncContext,
                             null /* no result since this is a cancel */);
                 } else {
@@ -1712,7 +1605,6 @@
                 if (isLoggable) {
                     Log.v(TAG, "runStateIdle: no data connection, skipping");
                 }
-                setStatusText("No data connection");
                 return;
             }
 
@@ -1720,7 +1612,6 @@
                 if (isLoggable) {
                     Log.v(TAG, "runStateIdle: memory low, skipping");
                 }
-                setStatusText("Memory low");
                 return;
             }
 
@@ -1731,7 +1622,6 @@
                 if (isLoggable) {
                     Log.v(TAG, "runStateIdle: accounts not known, skipping");
                 }
-                setStatusText("Accounts not known yet");
                 return;
             }
 
@@ -1742,8 +1632,9 @@
             final boolean backgroundDataUsageAllowed =
                     getConnectivityManager().getBackgroundDataSetting();
             synchronized (mSyncQueue) {
+                final long now = SystemClock.elapsedRealtime();
                 while (true) {
-                    op = mSyncQueue.head();
+                    op = mSyncQueue.nextReadyToRun(now);
                     if (op == null) {
                         if (isLoggable) {
                             Log.v(TAG, "runStateIdle: no more sync operations, returning");
@@ -1751,73 +1642,57 @@
                         return;
                     }
 
+                    // we are either going to run this sync or drop it so go ahead and remove it
+                    // 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);
                         }
-                        mSyncQueue.popHead();
-                        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();
-                    boolean syncAllowed =
-                            manualSync || (backgroundDataUsageAllowed && syncAutomatically);
-                    int isSyncable = mSyncStorageEngine.getIsSyncable(op.account, op.authority);
-                    if (isSyncable == 0) {
-                        // if not syncable, don't allow
-                        syncAllowed = false;
-                    } else if (isSyncable < 0) {
-                        // if the syncable state is unknown, only allow initialization syncs
-                        syncAllowed =
-                                op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
-                    }
-                    if (!syncAllowed) {
-                        if (isLoggable) {
-                            Log.v(TAG, "runStateIdle: sync off, dropping " + op);
-                        }
-                        mSyncQueue.popHead();
                         continue;
                     }
 
                     // skip the sync if the account of this operation no longer exists
                     if (!ArrayUtils.contains(accounts, op.account)) {
-                        mSyncQueue.popHead();
                         if (isLoggable) {
                             Log.v(TAG, "runStateIdle: account not present, dropping " + op);
                         }
                         continue;
                     }
 
-                    // go ahead and try to sync this syncOperation
-                    if (isLoggable) {
-                        Log.v(TAG, "runStateIdle: found sync candidate: " + op);
+                    // 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);
+                        }
+                        continue;
+                    }
+
+                    // go ahead and try to sync this syncOperation
                     break;
                 }
 
-                // If the first SyncOperation isn't ready to run schedule a wakeup and
-                // get out.
-                final long now = SystemClock.elapsedRealtime();
-                if (op.earliestRunTime > now) {
-                    if (Log.isLoggable(TAG, Log.DEBUG)) {
-                        Log.d(TAG, "runStateIdle: the time is " + now + " yet the next "
-                                + "sync operation is for " + op.earliestRunTime + ": " + op);
-                    }
-                    return;
-                }
-
-                // We will do this sync. Remove it from the queue and run it outside of the
-                // synchronized block.
+                // We will do this sync. Run it outside of the synchronized block.
                 if (isLoggable) {
                     Log.v(TAG, "runStateIdle: we are going to sync " + op);
                 }
-                mSyncQueue.popHead();
             }
 
             // connect to the sync adapter
@@ -1825,9 +1700,7 @@
             RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
                     mSyncAdapters.getServiceInfo(syncAdapterType);
             if (syncAdapterInfo == null) {
-                if (Config.LOGD) {
-                    Log.d(TAG, "can't find a sync adapter for " + syncAdapterType);
-                }
+                Log.d(TAG, "can't find a sync adapter for " + syncAdapterType);
                 runStateIdle();
                 return;
             }
@@ -1861,25 +1734,22 @@
                 syncAdapter.startSync(mActiveSyncContext, syncOperation.authority,
                         syncOperation.account, syncOperation.extras);
             } catch (RemoteException remoteExc) {
-                if (Config.LOGD) {
-                    Log.d(TAG, "runStateIdle: caught a RemoteException, rescheduling", remoteExc);
-                }
+                Log.d(TAG, "runStateIdle: caught a RemoteException, rescheduling", remoteExc);
                 mActiveSyncContext.unBindFromSyncAdapter();
                 mActiveSyncContext = null;
                 mSyncStorageEngine.setActiveSync(mActiveSyncContext);
-                rescheduleWithDelay(syncOperation);
+                increaseBackoffSetting(syncOperation);
+                scheduleSyncOperation(new SyncOperation(syncOperation));
             } catch (RuntimeException exc) {
                 mActiveSyncContext.unBindFromSyncAdapter();
                 mActiveSyncContext = null;
                 mSyncStorageEngine.setActiveSync(mActiveSyncContext);
-                Log.e(TAG, "Caught a RuntimeException while starting the sync " + syncOperation,
-                        exc);
+                Log.e(TAG, "Caught RuntimeException while starting the sync " + syncOperation, exc);
             }
         }
 
         private void runSyncFinishedOrCanceled(SyncResult syncResult) {
             boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
-            if (isLoggable) Log.v(TAG, "runSyncFinishedOrCanceled");
             final ActiveSyncContext activeSyncContext = mActiveSyncContext;
             mActiveSyncContext = null;
             mSyncStorageEngine.setActiveSync(mActiveSyncContext);
@@ -1893,32 +1763,34 @@
             int upstreamActivity;
             if (syncResult != null) {
                 if (isLoggable) {
-                    Log.v(TAG, "runSyncFinishedOrCanceled: is a finished: operation "
+                    Log.v(TAG, "runSyncFinishedOrCanceled [finished]: "
                             + syncOperation + ", result " + syncResult);
                 }
 
                 if (!syncResult.hasError()) {
-                    if (isLoggable) {
-                        Log.v(TAG, "finished sync operation " + syncOperation);
-                    }
                     historyMessage = SyncStorageEngine.MESG_SUCCESS;
                     // TODO: set these correctly when the SyncResult is extended to include it
                     downstreamActivity = 0;
                     upstreamActivity = 0;
+                    clearBackoffSetting(syncOperation);
                 } else {
-                    maybeRescheduleSync(syncResult, syncOperation);
-                    if (Config.LOGD) {
-                        Log.d(TAG, "failed sync operation " + syncOperation);
+                    Log.d(TAG, "failed sync operation " + syncOperation + ", " + syncResult);
+                    // the operation failed so increase the backoff time
+                    if (!syncResult.syncAlreadyInProgress) {
+                        increaseBackoffSetting(syncOperation);
                     }
+                    // reschedule the sync if so indicated by the syncResult
+                    maybeRescheduleSync(syncResult, syncOperation);
                     historyMessage = Integer.toString(syncResultToErrorNumber(syncResult));
                     // TODO: set these correctly when the SyncResult is extended to include it
                     downstreamActivity = 0;
                     upstreamActivity = 0;
                 }
+
+                setDelayUntilTime(syncOperation, syncResult.delayUntil);
             } else {
                 if (isLoggable) {
-                    Log.v(TAG, "runSyncFinishedOrCanceled: is a cancel: operation "
-                            + syncOperation);
+                    Log.v(TAG, "runSyncFinishedOrCanceled [canceled]: " + syncOperation);
                 }
                 if (activeSyncContext.mSyncAdapter != null) {
                     try {
@@ -2070,19 +1942,17 @@
             if (mAccounts == null) return;
             if (mStorageIsLow) return;
 
+            final long now = SystemClock.elapsedRealtime();
+
             // Compute the alarm fire time:
             // - not syncing: time of the next sync operation
             // - syncing, no notification: time from sync start to notification create time
             // - syncing, with notification: time till timeout of the active sync operation
-            Long alarmTime = null;
+            Long alarmTime;
             ActiveSyncContext activeSyncContext = mActiveSyncContext;
             if (activeSyncContext == null) {
-                SyncOperation syncOperation;
                 synchronized (mSyncQueue) {
-                    syncOperation = mSyncQueue.head();
-                }
-                if (syncOperation != null) {
-                    alarmTime = syncOperation.earliestRunTime;
+                    alarmTime = mSyncQueue.nextRunTime(now);
                 }
             } else {
                 final long notificationTime =
@@ -2104,7 +1974,7 @@
                     when += ERROR_NOTIFICATION_DELAY_MS;
                     // convert when fron absolute time to elapsed run time
                     long delay = when - System.currentTimeMillis();
-                    when = SystemClock.elapsedRealtime() + delay;
+                    when = now + delay;
                     alarmTime = alarmTime != null ? Math.min(alarmTime, when) : when;
                 }
             }
@@ -2228,221 +2098,4 @@
         }
     }
 
-    static class SyncQueue {
-        private SyncStorageEngine mSyncStorageEngine;
-
-        private static final boolean DEBUG_CHECK_DATA_CONSISTENCY = false;
-
-        // A priority queue of scheduled SyncOperations that is designed to make it quick
-        // to find the next SyncOperation that should be considered for running.
-        private final PriorityQueue<SyncOperation> mOpsByWhen = new PriorityQueue<SyncOperation>();
-
-        // A Map of SyncOperations operationKey -> SyncOperation that is designed for
-        // quick lookup of an enqueued SyncOperation.
-        private final HashMap<String, SyncOperation> mOpsByKey = Maps.newHashMap();
-
-        public SyncQueue(SyncStorageEngine syncStorageEngine) {
-            mSyncStorageEngine = syncStorageEngine;
-            ArrayList<SyncStorageEngine.PendingOperation> ops
-                    = mSyncStorageEngine.getPendingOperations();
-            final int N = ops.size();
-            for (int i=0; i<N; i++) {
-                SyncStorageEngine.PendingOperation op = ops.get(i);
-                SyncOperation syncOperation = new SyncOperation(
-                        op.account, op.syncSource, op.authority, op.extras, 0);
-                syncOperation.pendingOperation = op;
-                add(syncOperation, op);
-            }
-
-            if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
-        }
-
-        public boolean add(SyncOperation operation) {
-            return add(new SyncOperation(operation),
-                    null /* this is not coming from the database */);
-        }
-
-        private boolean add(SyncOperation operation,
-                SyncStorageEngine.PendingOperation pop) {
-            if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(pop == null);
-
-            // If this operation is expedited then set its earliestRunTime to be immediately
-            // before the head of the list, or not if none are in the list.
-            if (operation.delay < 0) {
-                SyncOperation headOperation = head();
-                if (headOperation != null) {
-                    operation.earliestRunTime = Math.min(SystemClock.elapsedRealtime(),
-                            headOperation.earliestRunTime - 1);
-                } else {
-                    operation.earliestRunTime = SystemClock.elapsedRealtime();
-                }
-            }
-
-            // - if an operation with the same key exists and this one should run earlier,
-            //   delete the old one and add the new one
-            // - if an operation with the same key exists and if this one should run
-            //   later, ignore it
-            // - if no operation exists then add the new one
-            final String operationKey = operation.key;
-            SyncOperation existingOperation = mOpsByKey.get(operationKey);
-
-            // if this operation matches an existing operation that is being retried (delay > 0)
-            // and this isn't a manual sync operation, ignore this operation
-            if (existingOperation != null && existingOperation.delay > 0) {
-                if (!operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)) {
-                    return false;
-                }
-            }
-
-            if (existingOperation != null
-                    && operation.earliestRunTime >= existingOperation.earliestRunTime) {
-                if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(pop == null);
-                return false;
-            }
-
-            if (existingOperation != null) {
-                removeByKey(operationKey);
-            }
-
-            operation.pendingOperation = pop;
-            if (operation.pendingOperation == null) {
-                pop = new SyncStorageEngine.PendingOperation(
-                                operation.account, operation.syncSource,
-                                operation.authority, operation.extras);
-                pop = mSyncStorageEngine.insertIntoPending(pop);
-                if (pop == null) {
-                    throw new IllegalStateException("error adding pending sync operation "
-                            + operation);
-                }
-                operation.pendingOperation = pop;
-            }
-
-            if (DEBUG_CHECK_DATA_CONSISTENCY) {
-                debugCheckDataStructures(
-                        false /* don't compare with the DB, since we know
-                               it is inconsistent right now */ );
-            }
-            mOpsByKey.put(operationKey, operation);
-            mOpsByWhen.add(operation);
-            if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(pop == null);
-            return true;
-        }
-
-        public void removeByKey(String operationKey) {
-            if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
-            SyncOperation operationToRemove = mOpsByKey.remove(operationKey);
-            if (!mOpsByWhen.remove(operationToRemove)) {
-                throw new IllegalStateException(
-                        "unable to find " + operationToRemove + " in mOpsByWhen");
-            }
-
-            if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) {
-                final String errorMessage = "unable to find pending row for " + operationToRemove;
-                Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
-            }
-
-            if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
-        }
-
-        public SyncOperation head() {
-            if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
-            return mOpsByWhen.peek();
-        }
-
-        public void popHead() {
-            if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
-            SyncOperation operation = mOpsByWhen.remove();
-            if (mOpsByKey.remove(operation.key) == null) {
-                throw new IllegalStateException("unable to find " + operation + " in mOpsByKey");
-            }
-
-            if (!mSyncStorageEngine.deleteFromPending(operation.pendingOperation)) {
-                final String errorMessage = "unable to find pending row for " + operation;
-                Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
-            }
-
-            if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
-        }
-
-        public void clear(Account account, String authority) {
-            Iterator<Map.Entry<String, SyncOperation>> entries = mOpsByKey.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 (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
-                entries.remove();
-                if (!mOpsByWhen.remove(syncOperation)) {
-                    throw new IllegalStateException(
-                            "unable to find " + syncOperation + " in mOpsByWhen");
-                }
-
-                if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) {
-                    final String errorMessage = "unable to find pending row for " + syncOperation;
-                    Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
-                }
-
-                if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
-            }
-        }
-
-        public void dump(StringBuilder sb) {
-            sb.append("SyncQueue: ").append(mOpsByWhen.size()).append(" operation(s)\n");
-            for (SyncOperation operation : mOpsByWhen) {
-                sb.append(operation).append("\n");
-            }
-        }
-
-        private void debugCheckDataStructures(boolean checkDatabase) {
-            if (mOpsByKey.size() != mOpsByWhen.size()) {
-                throw new IllegalStateException("size mismatch: "
-                        + mOpsByKey .size() + " != " + mOpsByWhen.size());
-            }
-            for (SyncOperation operation : mOpsByWhen) {
-                if (!mOpsByKey.containsKey(operation.key)) {
-                    throw new IllegalStateException(
-                        "operation " + operation + " is in mOpsByWhen but not mOpsByKey");
-                }
-            }
-            for (Map.Entry<String, SyncOperation> entry : mOpsByKey.entrySet()) {
-                final SyncOperation operation = entry.getValue();
-                final String key = entry.getKey();
-                if (!key.equals(operation.key)) {
-                    throw new IllegalStateException(
-                        "operation " + operation + " in mOpsByKey doesn't match key " + key);
-                }
-                if (!mOpsByWhen.contains(operation)) {
-                    throw new IllegalStateException(
-                        "operation " + operation + " is in mOpsByKey but not mOpsByWhen");
-                }
-            }
-
-            if (checkDatabase) {
-                final int N = mSyncStorageEngine.getPendingOperationCount();
-                if (mOpsByKey.size() != N) {
-                    ArrayList<SyncStorageEngine.PendingOperation> ops
-                            = mSyncStorageEngine.getPendingOperations();
-                    StringBuilder sb = new StringBuilder();
-                    for (int i=0; i<N; i++) {
-                        SyncStorageEngine.PendingOperation op = ops.get(i);
-                        sb.append("#");
-                        sb.append(i);
-                        sb.append(": account=");
-                        sb.append(op.account);
-                        sb.append(" syncSource=");
-                        sb.append(op.syncSource);
-                        sb.append(" authority=");
-                        sb.append(op.authority);
-                        sb.append("\n");
-                    }
-                    dump(sb);
-                    throw new IllegalStateException("DB size mismatch: "
-                            + mOpsByKey.size() + " != " + N + "\n"
-                            + sb.toString());
-                }
-            }
-        }
-    }
 }
diff --git a/core/java/android/content/SyncOperation.java b/core/java/android/content/SyncOperation.java
new file mode 100644
index 0000000..2d6e833
--- /dev/null
+++ b/core/java/android/content/SyncOperation.java
@@ -0,0 +1,95 @@
+package android.content;
+
+import android.accounts.Account;
+import android.os.Bundle;
+import android.os.SystemClock;
+
+/**
+ * Value type that represents a sync operation.
+ * @hide
+ */
+public class SyncOperation implements Comparable {
+    public final Account account;
+    public int syncSource;
+    public String authority;
+    public Bundle extras;
+    public final String key;
+    public long earliestRunTime;
+    public boolean expedited;
+    public SyncStorageEngine.PendingOperation pendingOperation;
+
+    public SyncOperation(Account account, int source, String authority, Bundle extras,
+            long delay) {
+        this.account = account;
+        this.syncSource = source;
+        this.authority = authority;
+        this.extras = new Bundle(extras);
+        removeFalseExtra(ContentResolver.SYNC_EXTRAS_UPLOAD);
+        removeFalseExtra(ContentResolver.SYNC_EXTRAS_MANUAL);
+        removeFalseExtra(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS);
+        removeFalseExtra(ContentResolver.SYNC_EXTRAS_EXPEDITED);
+        removeFalseExtra(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS);
+        final long now = SystemClock.elapsedRealtime();
+        if (delay < 0) {
+            this.expedited = true;
+            this.earliestRunTime = now;
+        } else {
+            this.expedited = false;
+            this.earliestRunTime = now + delay;
+        }
+        this.key = toKey();
+    }
+
+    private void removeFalseExtra(String extraName) {
+        if (!extras.getBoolean(extraName, false)) {
+            extras.remove(extraName);
+        }
+    }
+
+    SyncOperation(SyncOperation other) {
+        this.account = other.account;
+        this.syncSource = other.syncSource;
+        this.authority = other.authority;
+        this.extras = new Bundle(other.extras);
+        this.expedited = other.expedited;
+        this.earliestRunTime = SystemClock.elapsedRealtime();
+        this.key = toKey();
+    }
+
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("authority: ").append(authority);
+        sb.append(" account: ").append(account);
+        sb.append(" extras: ");
+        extrasToStringBuilder(extras, sb);
+        sb.append(" syncSource: ").append(syncSource);
+        sb.append(" when: ").append(earliestRunTime);
+        sb.append(" expedited: ").append(expedited);
+        return sb.toString();
+    }
+
+    private String toKey() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("authority: ").append(authority);
+        sb.append(" account: ").append(account);
+        sb.append(" extras: ");
+        extrasToStringBuilder(extras, sb);
+        return sb.toString();
+    }
+
+    public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
+        sb.append("[");
+        for (String key : bundle.keySet()) {
+            sb.append(key).append("=").append(bundle.get(key)).append(" ");
+        }
+        sb.append("]");
+    }
+
+    public int compareTo(Object o) {
+        SyncOperation other = (SyncOperation)o;
+        if (earliestRunTime == other.earliestRunTime) {
+            return 0;
+        }
+        return (earliestRunTime < other.earliestRunTime) ? -1 : 1;
+    }
+}
diff --git a/core/java/android/content/SyncQueue.java b/core/java/android/content/SyncQueue.java
new file mode 100644
index 0000000..a9f15d9
--- /dev/null
+++ b/core/java/android/content/SyncQueue.java
@@ -0,0 +1,190 @@
+package android.content;
+
+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;
+
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Iterator;
+
+/**
+ *
+ * @hide
+ */
+public class SyncQueue {
+    private static final String TAG = "SyncManager";
+    private SyncStorageEngine mSyncStorageEngine;
+
+    // A Map of SyncOperations operationKey -> SyncOperation that is designed for
+    // quick lookup of an enqueued SyncOperation.
+    private final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap();
+
+    public SyncQueue(SyncStorageEngine syncStorageEngine) {
+        mSyncStorageEngine = syncStorageEngine;
+        ArrayList<SyncStorageEngine.PendingOperation> ops
+                = mSyncStorageEngine.getPendingOperations();
+        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);
+            syncOperation.pendingOperation = op;
+            add(syncOperation, op);
+        }
+    }
+
+    public boolean add(SyncOperation operation) {
+        return add(operation, null /* this is not coming from the database */);
+    }
+
+    private boolean add(SyncOperation operation,
+            SyncStorageEngine.PendingOperation pop) {
+        // - if an operation with the same key exists and this one should run earlier,
+        //   update the earliestRunTime of the existing to the new time
+        // - if an operation with the same key exists and if this one should run
+        //   later, ignore it
+        // - if no operation exists then add the new one
+        final String operationKey = operation.key;
+        final SyncOperation existingOperation = mOperationsMap.get(operationKey);
+
+        if (existingOperation != null) {
+            boolean changed = false;
+            if (existingOperation.expedited == operation.expedited) {
+                final long newRunTime =
+                        Math.min(existingOperation.earliestRunTime, operation.earliestRunTime);
+                if (existingOperation.earliestRunTime != newRunTime) {
+                    existingOperation.earliestRunTime = newRunTime;
+                    changed = true;
+                }
+            } else {
+                if (operation.expedited) {
+                    existingOperation.expedited = true;
+                    changed = true;
+                }
+            }
+            return changed;
+        }
+
+        operation.pendingOperation = pop;
+        if (operation.pendingOperation == null) {
+            pop = new SyncStorageEngine.PendingOperation(
+                            operation.account, operation.syncSource,
+                            operation.authority, operation.extras, operation.expedited);
+            pop = mSyncStorageEngine.insertIntoPending(pop);
+            if (pop == null) {
+                throw new IllegalStateException("error adding pending sync operation "
+                        + operation);
+            }
+            operation.pendingOperation = pop;
+        }
+
+        mOperationsMap.put(operationKey, operation);
+        return true;
+    }
+
+    public void remove(SyncOperation operation) {
+        SyncOperation operationToRemove = mOperationsMap.remove(operation.key);
+        if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) {
+            final String errorMessage = "unable to find pending row for " + operationToRemove;
+            Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
+        }
+    }
+
+    /**
+     * 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;
+        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;
+            }
+        }
+        if (lowestOp == 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;
+    }
+
+    /**
+     * Find and return the SyncOperation that should be run next and is ready to run.
+     * @param now the current {@link android.os.SystemClock#elapsedRealtime()}, used to
+     * 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);
+        if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) {
+            return null;
+        }
+        return nextOpAndRunTime.first;
+    }
+
+    public void clear(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;
+            entries.remove();
+            if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) {
+                final String errorMessage = "unable to find pending row for " + syncOperation;
+                Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
+            }
+        }
+    }
+
+    public void dump(StringBuilder sb) {
+        sb.append("SyncQueue: ").append(mOperationsMap.size()).append(" operation(s)\n");
+        for (SyncOperation operation : mOperationsMap.values()) {
+            sb.append(operation).append("\n");
+        }
+    }
+}
diff --git a/core/java/android/content/SyncResult.java b/core/java/android/content/SyncResult.java
index 57161b6..3fbe847 100644
--- a/core/java/android/content/SyncResult.java
+++ b/core/java/android/content/SyncResult.java
@@ -14,6 +14,9 @@
     public boolean fullSyncRequested;
     public boolean partialSyncUnavailable;
     public boolean moreRecordsToGet;
+
+    // in seconds since epoch
+    public long delayUntil;
     public final SyncStats stats;
     public static final SyncResult ALREADY_IN_PROGRESS;
 
@@ -32,6 +35,7 @@
         this.fullSyncRequested = false;
         this.partialSyncUnavailable = false;
         this.moreRecordsToGet = false;
+        this.delayUntil = 0;
         this.stats = new SyncStats();
     }
 
@@ -43,6 +47,7 @@
         fullSyncRequested = parcel.readInt() != 0;
         partialSyncUnavailable = parcel.readInt() != 0;
         moreRecordsToGet = parcel.readInt() != 0;
+        delayUntil = parcel.readLong();
         stats = new SyncStats(parcel);
     }
 
@@ -80,6 +85,7 @@
         fullSyncRequested = false;
         partialSyncUnavailable = false;
         moreRecordsToGet = false;
+        delayUntil = 0;
         stats.clear();
     }
 
@@ -105,6 +111,7 @@
         parcel.writeInt(fullSyncRequested ? 1 : 0);
         parcel.writeInt(partialSyncUnavailable ? 1 : 0);
         parcel.writeInt(moreRecordsToGet ? 1 : 0);
+        parcel.writeLong(delayUntil);
         stats.writeToParcel(parcel, flags);
     }
 
@@ -123,6 +130,7 @@
             sb.append(" partialSyncUnavailable: ").append(partialSyncUnavailable);
         }
         if (moreRecordsToGet) sb.append(" moreRecordsToGet: ").append(moreRecordsToGet);
+        if (delayUntil > 0) sb.append(" delayUntil: ").append(delayUntil);
         sb.append(stats);
         return sb.toString();
     }
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index 4c53201..db70096 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -36,10 +36,11 @@
 import android.os.Parcel;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
-import android.os.SystemProperties;
+import android.os.SystemClock;
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.Xml;
+import android.util.Pair;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -88,6 +89,8 @@
     /** Enum value for a user-initiated sync. */
     public static final int SOURCE_USER = 3;
 
+    public static final long NOT_IN_BACKOFF_MODE = -1;
+
     private static final Intent SYNC_CONNECTION_SETTING_CHANGED_INTENT =
             new Intent("com.android.sync.SYNC_CONN_STATUS_CHANGED");
 
@@ -117,16 +120,18 @@
         final int syncSource;
         final String authority;
         final Bundle extras;        // note: read-only.
+        final boolean expedited;
 
         int authorityId;
         byte[] flatExtras;
 
         PendingOperation(Account account, int source,
-                String authority, Bundle extras) {
+                String authority, Bundle extras, boolean expedited) {
             this.account = account;
             this.syncSource = source;
             this.authority = authority;
             this.extras = extras != null ? new Bundle(extras) : extras;
+            this.expedited = expedited;
             this.authorityId = -1;
         }
 
@@ -136,6 +141,7 @@
             this.authority = other.authority;
             this.extras = other.extras;
             this.authorityId = other.authorityId;
+            this.expedited = other.expedited;
         }
     }
 
@@ -155,6 +161,9 @@
         final int ident;
         boolean enabled;
         int syncable;
+        long backoffTime;
+        long backoffDelay;
+        long delayUntil;
 
         AuthorityInfo(Account account, String authority, int ident) {
             this.account = account;
@@ -162,6 +171,8 @@
             this.ident = ident;
             enabled = SYNC_ENABLED_DEFAULT;
             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
         }
     }
 
@@ -280,7 +291,7 @@
 
     public static void init(Context context) {
         if (sSyncStorageEngine != null) {
-            throw new IllegalStateException("already initialized");
+            return;
         }
         sSyncStorageEngine = new SyncStorageEngine(context);
     }
@@ -380,7 +391,7 @@
         }
 
         if (!wasEnabled && sync) {
-            mContext.getContentResolver().requestSync(account, providerName, new Bundle());
+            ContentResolver.requestSync(account, providerName, new Bundle());
         }
         reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
     }
@@ -424,11 +435,91 @@
         }
 
         if (oldState <= 0 && syncable > 0) {
-            mContext.getContentResolver().requestSync(account, providerName, new Bundle());
+            ContentResolver.requestSync(account, providerName, new Bundle());
         }
         reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
     }
 
+    public Pair<Long, Long> getBackoff(Account account, String providerName) {
+        synchronized (mAuthorities) {
+            AuthorityInfo authority = getAuthorityLocked(account, providerName, "getBackoff");
+            if (authority == null || authority.backoffTime < 0) {
+                return null;
+            }
+            return Pair.create(authority.backoffTime, authority.backoffDelay);
+        }
+    }
+
+    public void setBackoff(Account account, String providerName,
+            long nextSyncTime, long nextDelay) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "setBackoff: " + account + ", provider " + providerName
+                    + " -> nextSyncTime " + nextSyncTime + ", nextDelay " + nextDelay);
+        }
+        boolean changed = false;
+        synchronized (mAuthorities) {
+            if (account == null || providerName == null) {
+                for (AccountInfo accountInfo : mAccounts.values()) {
+                    if (account != null && !account.equals(accountInfo.account)) continue;
+                    for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) {
+                        if (providerName != null && !providerName.equals(authorityInfo.authority)) {
+                            continue;
+                        }
+                        if (authorityInfo.backoffTime != nextSyncTime
+                                || authorityInfo.backoffDelay != nextDelay) {
+                            authorityInfo.backoffTime = nextSyncTime;
+                            authorityInfo.backoffDelay = nextDelay;
+                            changed = true;
+                        }
+                    }
+                }
+            } else {
+                AuthorityInfo authority =
+                        getOrCreateAuthorityLocked(account, providerName, -1, false);
+                if (authority.backoffTime == nextSyncTime && authority.backoffDelay == nextDelay) {
+                    return;
+                }
+                authority.backoffTime = nextSyncTime;
+                authority.backoffDelay = nextDelay;
+                changed = true;
+            }
+            if (changed) {
+                writeAccountInfoLocked();
+            }
+        }
+
+        if (changed) {
+            reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+        }
+    }
+
+    public void setDelayUntilTime(Account account, String providerName, long delayUntil) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "setDelayUntil: " + account + ", provider " + providerName
+                    + " -> delayUntil " + delayUntil);
+        }
+        synchronized (mAuthorities) {
+            AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false);
+            if (authority.delayUntil == delayUntil) {
+                return;
+            }
+            authority.delayUntil = delayUntil;
+            writeAccountInfoLocked();
+        }
+
+        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+    }
+
+    public long getDelayUntilTime(Account account, String providerName) {
+        synchronized (mAuthorities) {
+            AuthorityInfo authority = getAuthorityLocked(account, providerName, "getDelayUntil");
+            if (authority == null) {
+                return 0;
+            }
+            return authority.delayUntil;
+        }
+    }
+
     public void setMasterSyncAutomatically(boolean flag) {
         boolean old;
         synchronized (mAuthorities) {
@@ -437,7 +528,7 @@
             writeAccountInfoLocked();
         }
         if (!old && flag) {
-            mContext.getContentResolver().requestSync(null, null, new Bundle());
+            ContentResolver.requestSync(null, null, new Bundle());
         }
         reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
         mContext.sendBroadcast(SYNC_CONNECTION_SETTING_CHANGED_INTENT);
@@ -512,9 +603,6 @@
 
             SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
             status.pending = true;
-            status.initialize = op.extras != null &&
-                 op.extras.containsKey(ContentResolver.SYNC_EXTRAS_INITIALIZE) &&
-                 op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE);
         }
 
         reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
@@ -1414,7 +1502,7 @@
         }
     }
 
-    public static final int PENDING_OPERATION_VERSION = 1;
+    public static final int PENDING_OPERATION_VERSION = 2;
 
     /**
      * Read all pending operations back in to the initial engine state.
@@ -1429,7 +1517,7 @@
             final int SIZE = in.dataSize();
             while (in.dataPosition() < SIZE) {
                 int version = in.readInt();
-                if (version != PENDING_OPERATION_VERSION) {
+                if (version != PENDING_OPERATION_VERSION && version != 1) {
                     Log.w(TAG, "Unknown pending operation version "
                             + version + "; dropping all ops");
                     break;
@@ -1437,6 +1525,12 @@
                 int authorityId = in.readInt();
                 int syncSource = in.readInt();
                 byte[] flatExtras = in.createByteArray();
+                boolean expedited;
+                if (version == PENDING_OPERATION_VERSION) {
+                    expedited = in.readInt() != 0;
+                } else {
+                    expedited = false;
+                }
                 AuthorityInfo authority = mAuthorities.get(authorityId);
                 if (authority != null) {
                     Bundle extras = null;
@@ -1445,12 +1539,13 @@
                     }
                     PendingOperation op = new PendingOperation(
                             authority.account, syncSource,
-                            authority.authority, extras);
+                            authority.authority, extras, expedited);
                     op.authorityId = authorityId;
                     op.flatExtras = flatExtras;
                     if (DEBUG_FILE) Log.v(TAG, "Adding pending op: account=" + op.account
                             + " auth=" + op.authority
                             + " src=" + op.syncSource
+                            + " expedited=" + op.expedited
                             + " extras=" + op.extras);
                     mPendingOperations.add(op);
                 }
@@ -1468,6 +1563,7 @@
             op.flatExtras = flattenBundle(op.extras);
         }
         out.writeByteArray(op.flatExtras);
+        out.writeInt(op.expedited ? 1 : 0);
     }
 
     /**
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index a7ea507..808c839 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -234,6 +234,14 @@
     public static final int FLAG_ON_SDCARD = 1<<19;
 
     /**
+     * Value for {@link #flags}: Set to true if the application is
+     * native-debuggable, i.e. embeds a gdbserver binary in its .apk
+     *
+     * {@hide}
+     */
+    public static final int FLAG_NATIVE_DEBUGGABLE = 1<<20;
+
+    /**
      * Flags associated with the application.  Any combination of
      * {@link #FLAG_SYSTEM}, {@link #FLAG_DEBUGGABLE}, {@link #FLAG_HAS_CODE},
      * {@link #FLAG_PERSISTENT}, {@link #FLAG_FACTORY_TEST}, and
diff --git a/core/java/android/gesture/GestureStroke.java b/core/java/android/gesture/GestureStroke.java
index 68dc5a6..c3ddb28 100644
--- a/core/java/android/gesture/GestureStroke.java
+++ b/core/java/android/gesture/GestureStroke.java
@@ -31,7 +31,7 @@
  * consists of a sequence of timed points. One or multiple strokes form a gesture.
  */
 public class GestureStroke {
-    static final float TOUCH_TOLERANCE = 8;
+    static final float TOUCH_TOLERANCE = 3;
 
     public final RectF boundingBox;
 
diff --git a/core/java/android/gesture/GestureUtilities.java b/core/java/android/gesture/GestureUtilities.java
index dfe1d00..9d95ce4 100755
--- a/core/java/android/gesture/GestureUtilities.java
+++ b/core/java/android/gesture/GestureUtilities.java
@@ -26,7 +26,17 @@
 
 import static android.gesture.GestureConstants.*;
 
-final class GestureUtilities {
+/**
+ * Utility functions for gesture processing & analysis, including methods for:
+ * <ul> 
+ * <li>feature extraction (e.g., samplers and those for calculating bounding
+ * boxes and gesture path lengths);
+ * <li>geometric transformation (e.g., translation, rotation and scaling);
+ * <li>gesture similarity comparison (e.g., calculating Euclidean or Cosine
+ * distances between two gestures).
+ * </ul>
+ */
+public final class GestureUtilities {
   
     private static final float SCALING_THRESHOLD = 0.26f;
     private static final float NONUNIFORM_SCALE = (float) Math.sqrt(2);
@@ -49,14 +59,38 @@
         }
     }
     
-    static float[] spatialSampling(Gesture gesture, int sampleMatrixDimension) {
-        return spatialSampling(gesture, sampleMatrixDimension, false);
+    /**
+     * Samples the gesture spatially by rendering the gesture into a 2D 
+     * grayscale bitmap. Scales the gesture to fit the size of the bitmap. 
+     * The scaling does not necessarily keep the aspect ratio of the gesture. 
+     * 
+     * @param gesture the gesture to be sampled
+     * @param bitmapSize the size of the bitmap
+     * @return a bitmapSize x bitmapSize grayscale bitmap that is represented 
+     *         as a 1D array. The float at index i represents the grayscale 
+     *         value at pixel [i%bitmapSize, i/bitmapSize] 
+     */
+    public static float[] spatialSampling(Gesture gesture, int bitmapSize) {
+        return spatialSampling(gesture, bitmapSize, false);
     }
 
-    static float[] spatialSampling(Gesture gesture, int sampleMatrixDimension, 
-            boolean uniformScaling) {
-        final float targetPatchSize = sampleMatrixDimension - 1; // edge inclusive
-        float[] sample = new float[sampleMatrixDimension * sampleMatrixDimension];
+    /**
+     * Samples the gesture spatially by rendering the gesture into a 2D 
+     * grayscale bitmap. Scales the gesture to fit the size of the bitmap. 
+     * 
+     * @param gesture the gesture to be sampled
+     * @param bitmapSize the size of the bitmap
+     * @param keepAspectRatio if the scaling should keep the gesture's 
+     *        aspect ratio
+     * 
+     * @return a bitmapSize x bitmapSize grayscale bitmap that is represented 
+     *         as a 1D array. The float at index i represents the grayscale 
+     *         value at pixel [i%bitmapSize, i/bitmapSize] 
+     */
+    public static float[] spatialSampling(Gesture gesture, int bitmapSize, 
+            boolean keepAspectRatio) {
+        final float targetPatchSize = bitmapSize - 1; 
+        float[] sample = new float[bitmapSize * bitmapSize];
         Arrays.fill(sample, 0);
   
         RectF rect = gesture.getBoundingBox();
@@ -65,7 +99,7 @@
         float sx = targetPatchSize / gestureWidth;
         float sy = targetPatchSize / gestureHeight;
         
-        if (uniformScaling) {
+        if (keepAspectRatio) {
             float scale = sx < sy ? sx : sy;
             sx = scale;
             sy = scale;
@@ -122,16 +156,16 @@
                 if (segmentStartY > targetPatchSize) {
                     segmentStartY = targetPatchSize;
                 }
-                plot(segmentStartX, segmentStartY, sample, sampleMatrixDimension);
+                plot(segmentStartX, segmentStartY, sample, bitmapSize);
                 if (segmentEndX != -1) {
-                    // evaluate horizontally
+                    // Evaluate horizontally
                     if (segmentEndX > segmentStartX) {
                         xpos = (float) Math.ceil(segmentStartX);
                         float slope = (segmentEndY - segmentStartY) / 
                                       (segmentEndX - segmentStartX);
                         while (xpos < segmentEndX) {
                             ypos = slope * (xpos - segmentStartX) + segmentStartY;
-                            plot(xpos, ypos, sample, sampleMatrixDimension); 
+                            plot(xpos, ypos, sample, bitmapSize); 
                             xpos++;
                         }
                     } else if (segmentEndX < segmentStartX){
@@ -140,18 +174,18 @@
                                       (segmentEndX - segmentStartX);
                         while (xpos < segmentStartX) {
                             ypos = slope * (xpos - segmentStartX) + segmentStartY;
-                            plot(xpos, ypos, sample, sampleMatrixDimension); 
+                            plot(xpos, ypos, sample, bitmapSize); 
                             xpos++;
                         }
                     }
-                    // evaluating vertically
+                    // Evaluate vertically
                     if (segmentEndY > segmentStartY) {
                         ypos = (float) Math.ceil(segmentStartY);
                         float invertSlope = (segmentEndX - segmentStartX) / 
                                             (segmentEndY - segmentStartY);
                         while (ypos < segmentEndY) {
                             xpos = invertSlope * (ypos - segmentStartY) + segmentStartX;
-                            plot(xpos, ypos, sample, sampleMatrixDimension); 
+                            plot(xpos, ypos, sample, bitmapSize); 
                             ypos++;
                         }
                     } else if (segmentEndY < segmentStartY) {
@@ -160,7 +194,7 @@
                                             (segmentEndY - segmentStartY);
                         while (ypos < segmentStartY) {
                             xpos = invertSlope * (ypos - segmentStartY) + segmentStartX; 
-                            plot(xpos, ypos, sample, sampleMatrixDimension); 
+                            plot(xpos, ypos, sample, bitmapSize); 
                             ypos++;
                         }
                     }
@@ -224,15 +258,16 @@
     }
 
     /**
-     * Featurizes a stroke into a vector of a given number of elements
+     * Samples a stroke temporally into a given number of evenly-distributed 
+     * points.
      * 
-     * @param stroke
-     * @param sampleSize
-     * @return a float array
+     * @param stroke the gesture stroke to be sampled
+     * @param numPoints the number of points
+     * @return the sampled points in the form of [x1, y1, x2, y2, ..., xn, yn]
      */
-    static float[] temporalSampling(GestureStroke stroke, int sampleSize) {
-        final float increment = stroke.length / (sampleSize - 1);
-        int vectorLength = sampleSize * 2;
+    public static float[] temporalSampling(GestureStroke stroke, int numPoints) {
+        final float increment = stroke.length / (numPoints - 1);
+        int vectorLength = numPoints * 2;
         float[] vector = new float[vectorLength];
         float distanceSoFar = 0;
         float[] pts = stroke.points;
@@ -287,9 +322,9 @@
     }
 
     /**
-     * Calculate the centroid 
+     * Calculates the centroid of a set of points.
      * 
-     * @param points
+     * @param points the points in the form of [x1, y1, x2, y2, ..., xn, yn]
      * @return the centroid
      */
     static float[] computeCentroid(float[] points) {
@@ -309,10 +344,10 @@
     }
 
     /**
-     * calculate the variance-covariance matrix, treat each point as a sample
+     * Calculates the variance-covariance matrix of a set of points.
      * 
-     * @param points
-     * @return the covariance matrix
+     * @param points the points in the form of [x1, y1, x2, y2, ..., xn, yn]
+     * @return the variance-covariance matrix
      */
     private static float[][] computeCoVariance(float[] points) {
         float[][] array = new float[2][2];
@@ -363,7 +398,7 @@
     }
 
     /**
-     * Calculate the squared Euclidean distance between two vectors
+     * Calculates the squared Euclidean distance between two vectors.
      * 
      * @param vector1
      * @param vector2
@@ -380,7 +415,7 @@
     }
 
     /**
-     * Calculate the cosine distance between two instances
+     * Calculates the cosine distance between two instances.
      * 
      * @param vector1
      * @param vector2
@@ -396,7 +431,7 @@
     }
     
     /**
-     * Calculate the "minimum" cosine distance between two instances
+     * Calculates the "minimum" cosine distance between two instances.
      * 
      * @param vector1
      * @param vector2
@@ -426,8 +461,13 @@
         }
     }
 
-
-    static OrientedBoundingBox computeOrientedBoundingBox(ArrayList<GesturePoint> originalPoints) {
+    /**
+     * Computes an oriented, minimum bounding box of a set of points.
+     * 
+     * @param originalPoints
+     * @return an oriented bounding box
+     */
+    public static OrientedBoundingBox computeOrientedBoundingBox(ArrayList<GesturePoint> originalPoints) {
         final int count = originalPoints.size();
         float[] points = new float[count * 2];
         for (int i = 0; i < count; i++) {
@@ -440,7 +480,13 @@
         return computeOrientedBoundingBox(points, meanVector);
     }
 
-    static OrientedBoundingBox computeOrientedBoundingBox(float[] originalPoints) {
+    /**
+     * Computes an oriented, minimum bounding box of a set of points.
+     * 
+     * @param originalPoints
+     * @return an oriented bounding box
+     */
+    public static OrientedBoundingBox computeOrientedBoundingBox(float[] originalPoints) {
         int size = originalPoints.length;
         float[] points = new float[size];
         for (int i = 0; i < size; i++) {
diff --git a/core/java/android/os/IMountService.aidl b/core/java/android/os/IMountService.aidl
index c0c2d03..2124e85 100644
--- a/core/java/android/os/IMountService.aidl
+++ b/core/java/android/os/IMountService.aidl
@@ -99,6 +99,11 @@
     void unmountSecureContainer(String id);
 
     /*
+     * Rename an unmounted secure container.
+     */
+    void renameSecureContainer(String oldId, String newId);
+
+    /*
      * Returns the filesystem path of a mounted secure container.
      */
     String getSecureContainerPath(String id);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index dc40113..8172ac4 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -450,6 +450,21 @@
     public static final String ACTION_SEARCH_SETTINGS =
         "android.search.action.SEARCH_SETTINGS";
 
+    /**
+     * Activity Action: Show general device information settings (serial
+     * number, software version, phone number, etc.).
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_DEVICE_INFO_SETTINGS =
+        "android.settings.DEVICE_INFO_SETTINGS";
+
     // End of Intent actions for Settings
 
     private static final String JID_RESOURCE_PREFIX = "android";
diff --git a/core/java/android/speech/IRecognitionListener.aidl b/core/java/android/speech/IRecognitionListener.aidl
index 2da2258..5b48bd2 100644
--- a/core/java/android/speech/IRecognitionListener.aidl
+++ b/core/java/android/speech/IRecognitionListener.aidl
@@ -17,7 +17,6 @@
 package android.speech;
 
 import android.os.Bundle;
-import android.speech.RecognitionResult;
 
 /**
  * Listener for speech recognition events, used with RecognitionService.
@@ -26,35 +25,55 @@
  *  {@hide}
  */
 interface IRecognitionListener {
-    /** Called when the endpointer is ready for the user to start speaking. */
-    void onReadyForSpeech(in Bundle noiseParams);
+    /**
+     * Called when the endpointer is ready for the user to start speaking.
+     *
+     * @param params parameters set by the recognition service. Reserved for future use.
+     */
+    void onReadyForSpeech(in Bundle params);
 
-    /** The user has started to speak. */
+    /**
+     * The user has started to speak.
+     */
     void onBeginningOfSpeech();
 
-    /** The sound level in the audio stream has changed. */
+    /**
+     * The sound level in the audio stream has changed.
+     *
+     * @param rmsdB the new RMS dB value
+     */
     void onRmsChanged(in float rmsdB);
 
     /**
-     * More sound has been received. Buffer is a byte buffer containing
-     * a sequence of 16-bit shorts. 
+     * More sound has been received.
+     *
+     * @param buffer the byte buffer containing a sequence of 16-bit shorts.
      */
     void onBufferReceived(in byte[] buffer);
 
-    /** Called after the user stops speaking. */
+    /**
+     * Called after the user stops speaking.
+     */
     void onEndOfSpeech();
 
     /**
-     * A network or recognition error occurred. The code is defined in
-     * {@link android.speech.RecognitionResult}
+     * A network or recognition error occurred.
+     *
+     * @param error code is defined in {@link RecognitionManager}
      */
     void onError(in int error);
 
-    /** 
+    /**
      * Called when recognition results are ready.
-     * @param results: an ordered list of the most likely results (N-best list).
-     * @param key: a key associated with the results. The same results can
-     * be retrieved asynchronously later using the key, if available. 
+     *
+     * @param results a Bundle containing the most likely results (N-best list).
      */
-    void onResults(in List<RecognitionResult> results, long key);
+    void onResults(in Bundle results);
+
+     /**
+     * Called when recognition partial results are ready.
+     *
+     * @param results a Bundle containing the current most likely result.
+     */
+    void onPartialResults(in Bundle results);
 }
diff --git a/core/java/android/speech/IRecognitionService.aidl b/core/java/android/speech/IRecognitionService.aidl
index a18c380..ca9af15 100644
--- a/core/java/android/speech/IRecognitionService.aidl
+++ b/core/java/android/speech/IRecognitionService.aidl
@@ -16,22 +16,41 @@
 
 package android.speech;
 
+import android.os.Bundle;
 import android.content.Intent;
 import android.speech.IRecognitionListener;
-import android.speech.RecognitionResult;
 
-// A Service interface to speech recognition. Call startListening when
-// you want to begin capturing audio; RecognitionService will automatically
-// determine when the user has finished speaking, stream the audio to the
-// recognition servers, and notify you when results are ready.
-/** {@hide} */
+/**
+* A Service interface to speech recognition. Call startListening when
+* you want to begin capturing audio; RecognitionService will automatically
+* determine when the user has finished speaking, stream the audio to the
+* recognition servers, and notify you when results are ready. In most of the cases, 
+* this class should not be used directly, instead use {@link RecognitionManager} for
+* accessing recognition service. 
+* {@hide}
+*/
 interface IRecognitionService {
-    // Start listening for speech. Can only call this from one thread at once.
-    // see RecognizerIntent.java for constants used to specify the intent.
-    void startListening(in Intent recognizerIntent,
-        in IRecognitionListener listener);
-        
-    List<RecognitionResult> getRecognitionResults(in long key);
+    /**
+     * Starts listening for speech. Please note that the recognition service supports
+     * one listener only, therefore, if this function is called from two different threads,
+     * only the latest one will get the notifications
+     *
+     * @param recognizerIntent the intent from which the invocation occurred. Additionally,
+     *        this intent can contain extra parameters to manipulate the behavior of the recognition
+     *        client. For more information see {@link RecognizerIntent}.
+     * @param listener to receive callbacks
+     */
+    void startListening(in Intent recognizerIntent, in IRecognitionListener listener);
 
+    /**
+     * Stops listening for speech. Speech captured so far will be recognized as
+     * if the user had stopped speaking at this point. The function has no effect unless it
+     * is called during the speech capturing.
+     */
+    void stopListening();
+
+    /**
+     * Cancels the speech recognition.
+     */
     void cancel();
 }
diff --git a/core/java/android/speech/RecognitionListener.java b/core/java/android/speech/RecognitionListener.java
new file mode 100644
index 0000000..eab3f40
--- /dev/null
+++ b/core/java/android/speech/RecognitionListener.java
@@ -0,0 +1,96 @@
+/*
+ * 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.speech;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Used for receiving notifications from the RecognitionManager when the
+ * recognition related events occur. All the callbacks are executed on the
+ * Application main thread.
+ */
+public interface RecognitionListener {
+
+    /**
+     * Called when RecognitionManager is successfully initialized
+     */
+    void onInit();
+
+    /**
+     * Called when the endpointer is ready for the user to start speaking.
+     * 
+     * @param params parameters set by the recognition service. Reserved for future use.
+     */
+    void onReadyForSpeech(Bundle params);
+
+    /**
+     * The user has started to speak.
+     */
+    void onBeginningOfSpeech();
+
+    /**
+     * The sound level in the audio stream has changed. There is no guarantee that this method will
+     * be called.
+     * 
+     * @param rmsdB the new RMS dB value
+     */
+    void onRmsChanged(float rmsdB);
+
+    /**
+     * More sound has been received. The purpose of this function is to allow giving feedback to the
+     * user regarding the captured audio. There is no guarantee that this method will be called.
+     * 
+     * @param buffer a buffer containing a sequence of big-endian 16-bit integers representing a
+     *        single channel audio stream. The sample rate is implementation dependent.
+     */
+    void onBufferReceived(byte[] buffer);
+
+    /**
+     * Called after the user stops speaking.
+     */
+    void onEndOfSpeech();
+
+    /**
+     * A network or recognition error occurred.
+     * 
+     * @param error code is defined in {@link RecognitionManager}
+     */
+    void onError(int error);
+
+    /**
+     * Called when recognition results are ready.
+     * 
+     * @param results the recognition results. To retrieve the results in {@code
+     *        ArrayList&lt;String&gt;} format use {@link Bundle#getStringArrayList(String)} with
+     *        {@link RecognitionManager#RECOGNITION_RESULTS_STRING_ARRAY} as a parameter
+     */
+    void onResults(Bundle results);
+
+    /**
+     * Called when partial recognition results are available. The callback might be called at any
+     * time between {@link #onBeginningOfSpeech()} and {@link #onResults(Bundle)} when partial
+     * results are ready. This method may be called zero, one or multiple times for each call to
+     * {@link RecognitionManager#startListening(Intent)}, depending on the speech recognition
+     * service implementation.
+     * 
+     * @param partialResults the returned results. To retrieve the results in
+     *        ArrayList&lt;String&gt; format use {@link Bundle#getStringArrayList(String)} with
+     *        {@link RecognitionManager#RECOGNITION_RESULTS_STRING_ARRAY} as a parameter
+     */
+    void onPartialResults(Bundle partialResults);
+
+}
diff --git a/core/java/android/speech/RecognitionManager.java b/core/java/android/speech/RecognitionManager.java
new file mode 100644
index 0000000..79ae480
--- /dev/null
+++ b/core/java/android/speech/RecognitionManager.java
@@ -0,0 +1,321 @@
+/*
+ * 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.speech;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * This class provides access to the speech recognition service. This service allows access to the
+ * speech recognizer. Do not instantiate this class directly, instead, call
+ * {@link RecognitionManager#createRecognitionManager(Context, RecognitionListener, Intent)}. This
+ * class is not thread safe and must be synchronized externally if accessed from multiple threads.
+ */
+public class RecognitionManager {
+    /** DEBUG value to enable verbose debug prints */
+    private final static boolean DBG = false;
+
+    /** Log messages identifier */
+    private static final String TAG = "RecognitionManager";
+
+    /**
+     * Used to retrieve an {@code ArrayList&lt;String&gt;} from the {@link Bundle} passed to the
+     * {@link RecognitionListener#onResults(Bundle)} and
+     * {@link RecognitionListener#onPartialResults(Bundle)} methods. These strings are the possible
+     * recognition results, where the first element is the most likely candidate.
+     */
+    public static final String RECOGNITION_RESULTS_STRING_ARRAY =
+            "recognition_results_string_array";
+
+    /** The actual RecognitionService endpoint */
+    private IRecognitionService mService;
+
+    /** The connection to the actual service */
+    private Connection mConnection;
+
+    /** Context with which the manager was created */
+    private final Context mContext;
+
+    /** Listener that will receive all the callbacks */
+    private final RecognitionListener mListener;
+
+    /** Helper class wrapping the IRecognitionListener */
+    private final InternalRecognitionListener mInternalRecognitionListener;
+
+    /** Network operation timed out. */
+    public static final int NETWORK_TIMEOUT_ERROR = 1;
+
+    /** Other network related errors. */
+    public static final int NETWORK_ERROR = 2;
+
+    /** Audio recording error. */
+    public static final int AUDIO_ERROR = 3;
+
+    /** Server sends error status. */
+    public static final int SERVER_ERROR = 4;
+
+    /** Other client side errors. */
+    public static final int CLIENT_ERROR = 5;
+
+    /** No speech input */
+    public static final int SPEECH_TIMEOUT_ERROR = 6;
+
+    /** No recognition result matched. */
+    public static final int NO_MATCH_ERROR = 7;
+
+    /** RecognitionService busy. */
+    public static final int SERVER_BUSY_ERROR = 8;
+
+    /**
+     * RecognitionManager was not initialized yet, most probably because
+     * {@link RecognitionListener#onInit()} was not called yet.
+     */
+    public static final int MANAGER_NOT_INITIALIZED_ERROR = 9;
+
+    /**
+     * The right way to create a RecognitionManager is by using
+     * {@link #createRecognitionManager} static factory method
+     */
+    private RecognitionManager(final RecognitionListener listener, final Context context) {
+        mInternalRecognitionListener = new InternalRecognitionListener();
+        mContext = context;
+        mListener = listener;
+    }
+
+    /**
+     * Basic ServiceConnection which just records mService variable.
+     */
+    private class Connection implements ServiceConnection {
+
+        public synchronized void onServiceConnected(final ComponentName name,
+                final IBinder service) {
+            mService = IRecognitionService.Stub.asInterface(service);
+            if (mListener != null) {
+                mListener.onInit();
+            }
+            if (DBG) Log.d(TAG, "onServiceConnected - Success");
+        }
+
+        public void onServiceDisconnected(final ComponentName name) {
+            mService = null;
+            mConnection = null;
+            if (DBG) Log.d(TAG, "onServiceDisconnected - Success");
+        }
+    }
+
+    /**
+     * Checks whether a speech recognition service is available on the system. If this method
+     * returns {@code false},
+     * {@link RecognitionManager#createRecognitionManager(Context, RecognitionListener, Intent)}
+     * will fail.
+     * 
+     * @param context with which RecognitionManager will be created
+     * @return {@code true} if recognition is available, {@code false} otherwise
+     */
+    public static boolean isRecognitionAvailable(final Context context) {
+        final List<ResolveInfo> list = context.getPackageManager().queryIntentServices(
+                new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0);
+        return list != null && list.size() != 0;
+    }
+
+    /**
+     * Factory method to create a new RecognitionManager
+     * 
+     * @param context in which to create RecognitionManager
+     * @param listener that will receive all the callbacks from the created
+     *        {@link RecognitionManager}
+     * @param recognizerIntent contains initialization parameters for the speech recognizer. The
+     *        intent action should be {@link RecognizerIntent#ACTION_RECOGNIZE_SPEECH}. Future
+     *        versions of this API may add startup parameters for speech recognizer.
+     * @return null if a recognition service implementation is not installed or if speech
+     *         recognition is not supported by the device, otherwise a new RecognitionManager is
+     *         returned. The created RecognitionManager can only be used after the
+     *         {@link RecognitionListener#onInit()} method has been called.
+     */
+    public static RecognitionManager createRecognitionManager(final Context context,
+            final RecognitionListener listener, final Intent recognizerIntent) {
+        if (context == null || recognizerIntent == null) {
+            throw new IllegalArgumentException(
+                    "Context and recognizerListener argument cannot be null)");
+        }
+        RecognitionManager manager = new RecognitionManager(listener, context);
+        manager.mConnection = manager.new Connection();
+        if (!context.bindService(recognizerIntent, manager.mConnection, Context.BIND_AUTO_CREATE)) {
+            Log.e(TAG, "bind to recognition service failed");
+            listener.onError(CLIENT_ERROR);
+            return null;
+        }
+        return manager;
+    }
+
+    /**
+     * Checks whether the service is connected
+     *
+     * @param functionName from which the call originated
+     * @return {@code true} if the service was successfully initialized, {@code false} otherwise
+     */
+    private boolean connectToService(final String functionName) {
+        if (mService != null) {
+            return true;
+        }
+        if (mConnection == null) {
+            if (DBG) Log.d(TAG, "restarting connection to the recognition service");
+            mConnection = new Connection();
+            mContext.bindService(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), mConnection,
+                    Context.BIND_AUTO_CREATE);
+        }
+        mInternalRecognitionListener.onError(MANAGER_NOT_INITIALIZED_ERROR);
+        Log.e(TAG, functionName + " was called before service connection was initialized");
+        return false;
+    }
+
+    /**
+     * Starts listening for speech.
+     * 
+     * @param recognizerIntent contains parameters for the recognition to be performed. The intent
+     *        action should be {@link RecognizerIntent#ACTION_RECOGNIZE_SPEECH}. The intent may also
+     *        contain optional extras, see {@link RecognizerIntent}. If these values are not set
+     *        explicitly, default values will be used by the recognizer.
+     */
+    public void startListening(Intent recognizerIntent) {
+        if (recognizerIntent == null) {
+            throw new IllegalArgumentException("recognizerIntent argument cannot be null");
+        }
+        if (!connectToService("startListening")) {
+            return; // service is not connected yet, reconnect in progress
+        }
+        try {
+            mService.startListening(recognizerIntent, mInternalRecognitionListener);
+            if (DBG) Log.d(TAG, "service start listening command succeded");
+        } catch (final RemoteException e) {
+            Log.e(TAG, "startListening() failed", e);
+            mInternalRecognitionListener.onError(CLIENT_ERROR);
+        }
+    }
+
+    /**
+     * Stops listening for speech. Speech captured so far will be recognized as if the user had
+     * stopped speaking at this point. Note that in the default case, this does not need to be
+     * called, as the speech endpointer will automatically stop the recognizer listening when it
+     * determines speech has completed. However, you can manipulate endpointer parameters directly
+     * using the intent extras defined in {@link RecognizerIntent}, in which case you may sometimes
+     * want to manually call this method to stop listening sooner.
+     */
+    public void stopListening() {
+        if (mService == null) {
+            return; // service is not connected, but no need to reconnect at this point
+        }
+        try {
+            mService.stopListening();
+            if (DBG) Log.d(TAG, "service stop listening command succeded");
+        } catch (final RemoteException e) {
+            Log.e(TAG, "stopListening() failed", e);
+            mInternalRecognitionListener.onError(CLIENT_ERROR);
+        }
+    }
+
+    /**
+     * Cancels the speech recognition.
+     */
+    public void cancel() {
+        if (mService == null) {
+            return; // service is not connected, but no need to reconnect at this point
+        }
+        try {
+            mService.cancel();
+            if (DBG) Log.d(TAG, "service cancel command succeded");
+        } catch (final RemoteException e) {
+            Log.e(TAG, "cancel() failed", e);
+            mInternalRecognitionListener.onError(CLIENT_ERROR);
+        }
+    }
+
+    /**
+     * Destroys the RecognitionManager object. Note that after calling this method all method calls
+     * on this object will fail, triggering {@link RecognitionListener#onError}.
+     */
+    public void destroy() {
+        if (mConnection != null) {
+            mContext.unbindService(mConnection);
+        }
+        mService = null;
+    }
+
+    /**
+     * Internal wrapper of IRecognitionListener which will propagate the results
+     * to RecognitionListener
+     */
+    private class InternalRecognitionListener extends IRecognitionListener.Stub {
+
+        public void onBeginningOfSpeech() {
+            if (mListener != null) {
+                mListener.onBeginningOfSpeech();
+            }
+        }
+
+        public void onBufferReceived(final byte[] buffer) {
+            if (mListener != null) {
+                mListener.onBufferReceived(buffer);
+            }
+        }
+
+        public void onEndOfSpeech() {
+            if (mListener != null) {
+                mListener.onEndOfSpeech();
+            }
+        }
+
+        public void onError(final int error) {
+            if (mListener != null) {
+                mListener.onError(error);
+            }
+        }
+
+        public void onReadyForSpeech(final Bundle noiseParams) {
+            if (mListener != null) {
+                mListener.onReadyForSpeech(noiseParams);
+            }
+        }
+
+        public void onResults(final Bundle results) {
+            if (mListener != null) {
+                mListener.onResults(results);
+            }
+        }
+
+        public void onPartialResults(final Bundle results) {
+            if (mListener != null) {
+                mListener.onPartialResults(results);
+            }
+        }
+
+        public void onRmsChanged(final float rmsdB) {
+            if (mListener != null) {
+                mListener.onRmsChanged(rmsdB);
+            }
+        }
+    }
+}
diff --git a/core/java/android/speech/RecognitionResult.aidl b/core/java/android/speech/RecognitionResult.aidl
deleted file mode 100644
index 59e53ab..0000000
--- a/core/java/android/speech/RecognitionResult.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.speech;
-
-parcelable RecognitionResult;
diff --git a/core/java/android/speech/RecognitionResult.java b/core/java/android/speech/RecognitionResult.java
deleted file mode 100644
index 95715ee..0000000
--- a/core/java/android/speech/RecognitionResult.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.speech;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * RecognitionResult is a passive object that stores a single recognized query
- * and its search result.
- * 
- * TODO: Revisit and improve this class, reconciling the different types of actions and
- * the different ways they are represented. Maybe we should have a separate result object
- * for each type, and put them (type/value) in bundle?
- * {@hide}
- */
-public class RecognitionResult implements Parcelable {
-    /**
-     * Status of the recognize request.
-     */
-    public static final int NETWORK_TIMEOUT = 1; // Network operation timed out.
-
-    public static final int NETWORK_ERROR = 2; // Other network related errors.
-
-    public static final int AUDIO_ERROR = 3; // Audio recording error.
-
-    public static final int SERVER_ERROR = 4; // Server sends error status.
-
-    public static final int CLIENT_ERROR = 5; // Other client side errors.
-
-    public static final int SPEECH_TIMEOUT = 6; // No speech input
-
-    public static final int NO_MATCH = 7; // No recognition result matched.
-
-    public static final int SERVICE_BUSY = 8; // RecognitionService busy.
-
-    /**
-     * Type of the recognition results.
-     */
-    public static final int RAW_RECOGNITION_RESULT = 0;
-
-    public static final int WEB_SEARCH_RESULT = 1;
-
-    public static final int CONTACT_RESULT = 2;
-    
-    public static final int ACTION_RESULT = 3;
-
-    /**
-     * A factory method to create a raw RecognitionResult
-     * 
-     * @param sentence the recognized text.
-     */
-    public static RecognitionResult newRawRecognitionResult(String sentence) {
-        return new RecognitionResult(RAW_RECOGNITION_RESULT, sentence, null, null);
-    }
-
-    /**
-     * A factory method to create a RecognitionResult for contacts.
-     * 
-     * @param contact the contact name.
-     * @param phoneType the phone type.
-     * @param callAction whether this result included a command to "call", or
-     *            just the contact name.
-     */
-    public static RecognitionResult newContactResult(String contact, int phoneType,
-            boolean callAction) {
-        return new RecognitionResult(CONTACT_RESULT, contact, phoneType, callAction);
-    }
-
-    /**
-     * A factory method to create a RecognitionResult for a web search query.
-     * 
-     * @param query the query string.
-     * @param html the html page of the search result.
-     * @param url the url that performs the search with the query.
-     */
-    public static RecognitionResult newWebResult(String query, String html, String url) {
-        return new RecognitionResult(WEB_SEARCH_RESULT, query, html, url);
-    }
-    
-    /**
-     * A factory method to create a RecognitionResult for an action.
-     * 
-     * @param action the action type
-     * @param query the query string associated with that action.
-     */
-    public static RecognitionResult newActionResult(int action, String query) {
-        return new RecognitionResult(ACTION_RESULT, action, query);
-    }
-
-    public static final Parcelable.Creator<RecognitionResult> CREATOR =
-            new Parcelable.Creator<RecognitionResult>() {
-
-                public RecognitionResult createFromParcel(Parcel in) {
-                    return new RecognitionResult(in);
-                }
-        
-                public RecognitionResult[] newArray(int size) {
-                    return new RecognitionResult[size];
-                }
-            };
-
-    /**
-     * Result type.
-     */
-    public final int mResultType;
-
-    /**
-     * The recognized string when mResultType is WEB_SEARCH_RESULT. The name of
-     * the contact when mResultType is CONTACT_RESULT. The relevant query when
-     * mResultType is ACTION_RESULT.
-     */
-    public final String mText;
-
-    /**
-     * The HTML result page for the query. If this is null, then the application
-     * must use the url field to get the HTML result page.
-     */
-    public final String mHtml;
-
-    /**
-     * The url to get the result page for the query string. The application must
-     * use this url instead of performing the search with the query.
-     */
-    public final String mUrl;
-
-    /**
-     * Phone number type. This is valid only when mResultType == CONTACT_RESULT.
-     */
-    public final int mPhoneType;
-    
-    /**
-     * Action type.  This is valid only when mResultType == ACTION_RESULT.
-     */
-    public final int mAction;
-
-    /**
-     * Whether a contact recognition result included a command to "call". This
-     * is valid only when mResultType == CONTACT_RESULT.
-     */
-    public final boolean mCallAction;
-
-    private RecognitionResult(int type, int action, String query) {
-        mResultType = type;
-        mAction = action;
-        mText = query;
-        mHtml = null;
-        mUrl = null;
-        mPhoneType = -1;
-        mCallAction = false;
-    }
-    
-    private RecognitionResult(int type, String query, String html, String url) {
-        mResultType = type;
-        mText = query;
-        mHtml = html;
-        mUrl = url;
-        mPhoneType = -1;
-        mAction = -1;
-        mCallAction = false;
-    }
-
-    private RecognitionResult(int type, String query, int phoneType, boolean callAction) {
-        mResultType = type;
-        mText = query;
-        mPhoneType = phoneType;
-        mHtml = null;
-        mUrl = null;
-        mAction = -1;
-        mCallAction = callAction;
-    }
-
-    private RecognitionResult(Parcel in) {
-        mResultType = in.readInt();
-        mText = in.readString();
-        mHtml = in.readString();
-        mUrl = in.readString();
-        mPhoneType = in.readInt();
-        mAction = in.readInt();
-        mCallAction = (in.readInt() == 1);
-    }
-
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeInt(mResultType);
-        out.writeString(mText);
-        out.writeString(mHtml);
-        out.writeString(mUrl);
-        out.writeInt(mPhoneType);
-        out.writeInt(mAction);
-        out.writeInt(mCallAction ? 1 : 0);
-    }
-
-    @Override
-    public String toString() {
-        String resultType[] = {
-                "RAW", "WEB", "CONTACT", "ACTION"
-        };
-        return "[type=" + resultType[mResultType] + ", text=" + mText + ", mUrl=" + mUrl
-                + ", html=" + mHtml + ", mAction=" + mAction + ", mCallAction=" + mCallAction + "]";
-    }
-
-    public int describeContents() {
-        // no special description
-        return 0;
-    }
-}
diff --git a/core/java/android/speech/RecognitionServiceUtil.java b/core/java/android/speech/RecognitionServiceUtil.java
deleted file mode 100644
index 4207543..0000000
--- a/core/java/android/speech/RecognitionServiceUtil.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.speech;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.speech.RecognitionResult;
-import android.util.Log;
-
-import java.util.List;
-
-/**
- * Utils for Google's network-based speech recognizer, which lets you perform
- * speech-to-text translation through RecognitionService. IRecognitionService
- * and IRecognitionListener are the core interfaces; you begin recognition
- * through IRecognitionService and subscribe to callbacks about when the user
- * stopped speaking, results come in, errors, etc. through IRecognitionListener.
- * RecognitionServiceUtil includes default IRecognitionListener and
- * ServiceConnection implementations to reduce the amount of boilerplate.
- *
- * The Service provides no user interface. See RecognitionActivity if you
- * want the standard voice search UI.
- *
- * Below is a small skeleton of how to use the recognizer:
- *
- * ServiceConnection conn = new RecognitionServiceUtil.Connection();
- * mContext.bindService(RecognitionServiceUtil.sDefaultIntent,
- *     conn, Context.BIND_AUTO_CREATE);
- * IRecognitionListener listener = new RecognitionServiceWrapper.NullListener() {
- *         public void onResults(List<String> results) {
- *             // Do something with recognition transcripts
- *         }
- *     }
- *
- * // Must wait for conn.mService to be populated, then call below
- * conn.mService.startListening(null, listener);
- *
- * {@hide}
- */
-public class RecognitionServiceUtil {
-    public static final Intent sDefaultIntent = new Intent(
-            RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
-
-    // Recognize request parameters
-    public static final String USE_LOCATION = "useLocation";
-    public static final String CONTACT_AUTH_TOKEN = "contactAuthToken";
-    
-    // Bundles
-    public static final String NOISE_LEVEL = "NoiseLevel";
-    public static final String SIGNAL_NOISE_RATIO = "SignalNoiseRatio";
-
-    private RecognitionServiceUtil() {}
-
-    /**
-     * IRecognitionListener which does nothing in response to recognition
-     * callbacks. You can subclass from this and override only the methods
-     * whose events you want to respond to.
-     */
-    public static class NullListener extends IRecognitionListener.Stub {
-        public void onReadyForSpeech(Bundle bundle) {}
-        public void onBeginningOfSpeech() {}
-        public void onRmsChanged(float rmsdB) {}
-        public void onBufferReceived(byte[] buf) {}
-        public void onEndOfSpeech() {}
-        public void onError(int error) {}
-        public void onResults(List<RecognitionResult> results, long key) {}
-    }
-
-    /**
-     * Basic ServiceConnection which just records mService variable.
-     */
-    public static class Connection implements ServiceConnection {
-        public IRecognitionService mService;
-
-        public synchronized void onServiceConnected(ComponentName name, IBinder service) {
-            mService = IRecognitionService.Stub.asInterface(service);
-        }
-
-        public void onServiceDisconnected(ComponentName name) {
-            mService = null;
-        }
-    }
-}
diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java
index 6c5f912..49991bd 100644
--- a/core/java/android/speech/RecognizerIntent.java
+++ b/core/java/android/speech/RecognizerIntent.java
@@ -84,6 +84,42 @@
     public static final String ACTION_WEB_SEARCH = "android.speech.action.WEB_SEARCH";
 
     /**
+     * The minimum length of an utterance. We will not stop recording before this amount of time.
+     * 
+     * Note that it is extremely rare you'd want to specify this value in an intent. If you don't
+     * have a very good reason to change these, you should leave them as they are. Note also that
+     * certain values may cause undesired or unexpected results - use judiciously! Additionally,
+     * depending on the recognizer implementation, these values may have no effect.
+     */
+    public static final String EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS =
+            "android.speech.extras.SPEECH_INPUT_MINIMUM_LENGTH_MILLIS";
+
+    /**
+     * The amount of time that it should take after we stop hearing speech to consider the input
+     * complete. 
+     * 
+     * Note that it is extremely rare you'd want to specify this value in an intent. If
+     * you don't have a very good reason to change these, you should leave them as they are. Note
+     * also that certain values may cause undesired or unexpected results - use judiciously!
+     * Additionally, depending on the recognizer implementation, these values may have no effect.
+     */
+    public static final String EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS =
+            "android.speech.extras.SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS";
+
+    /**
+     * The amount of time that it should take after we stop hearing speech to consider the input
+     * possibly complete. This is used to prevent the endpointer cutting off during very short
+     * mid-speech pauses. 
+     * 
+     * Note that it is extremely rare you'd want to specify this value in an intent. If
+     * you don't have a very good reason to change these, you should leave them as they are. Note
+     * also that certain values may cause undesired or unexpected results - use judiciously!
+     * Additionally, depending on the recognizer implementation, these values may have no effect.
+     */
+    public static final String EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS =
+            "android.speech.extras.SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS";
+
+    /**
      * Informs the recognizer which speech model to prefer when performing
      * {@link #ACTION_RECOGNIZE_SPEECH}. The recognizer uses this
      * information to fine tune the results. This extra is required. Activities implementing
@@ -111,8 +147,9 @@
     public static final String EXTRA_PROMPT = "android.speech.extra.PROMPT";
 
     /**
-     * Optional language override to inform the recognizer that it should expect speech in
-     * a language different than the one set in the {@link java.util.Locale#getDefault()}. 
+     * Optional IETF language tag (as defined by BCP 47), for example "en-US". This tag informs the
+     * recognizer to perform speech recognition in a language different than the one set in the
+     * {@link java.util.Locale#getDefault()}.
      */
     public static final String EXTRA_LANGUAGE = "android.speech.extra.LANGUAGE";
 
@@ -121,7 +158,7 @@
      * will choose how many results to return. Must be an integer.
      */
     public static final String EXTRA_MAX_RESULTS = "android.speech.extra.MAX_RESULTS";
-    
+
     /**
      * When the intent is {@link #ACTION_RECOGNIZE_SPEECH}, the speech input activity will
      * return results to you via the activity results mechanism.  Alternatively, if you use this
@@ -157,8 +194,6 @@
     
     /**
      * Triggers the voice search settings activity.
-     * 
-     * @hide pending API council approval, to be unhidden for Froyo
      */
     public static final String ACTION_VOICE_SEARCH_SETTINGS =
             "android.speech.action.VOICE_SEARCH_SETTINGS";
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index 50bfefc..f991df7 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -147,12 +147,9 @@
 
     public ScaleGestureDetector(Context context, OnScaleGestureListener listener) {
         ViewConfiguration config = ViewConfiguration.get(context);
-        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
         mContext = context;
         mListener = listener;
         mEdgeSlop = config.getScaledEdgeSlop();
-        mRightSlopEdge = metrics.widthPixels - mEdgeSlop;
-        mBottomSlopEdge = metrics.heightPixels - mEdgeSlop;
     }
 
     public boolean onTouchEvent(MotionEvent event) {
@@ -165,6 +162,11 @@
                     event.getPointerCount() >= 2) {
                 // We have a new multi-finger gesture
 
+                // as orientation can change, query the metrics in touch down
+                DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
+                mRightSlopEdge = metrics.widthPixels - mEdgeSlop;
+                mBottomSlopEdge = metrics.heightPixels - mEdgeSlop;
+
                 // Be paranoid in case we missed an event
                 reset();
 
@@ -185,12 +187,16 @@
                 final float x1 = getRawX(event, 1);
                 final float y1 = getRawY(event, 1);
 
-                boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop ||
-                        x1 < edgeSlop || y1 < edgeSlop;
-                boolean p1sloppy = x0 > rightSlop || y0 > bottomSlop ||
-                        x1 > rightSlop || y1 > bottomSlop;
+                boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop
+                        || x0 > rightSlop || y0 > bottomSlop;
+                boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop
+                        || x1 > rightSlop || y1 > bottomSlop;
 
-                if (p0sloppy) {
+                if(p0sloppy && p1sloppy) {
+                    mFocusX = -1;
+                    mFocusY = -1;
+                    mSloppyGesture = true;
+                } else if (p0sloppy) {
                     mFocusX = event.getX(1);
                     mFocusY = event.getY(1);
                     mSloppyGesture = true;
@@ -211,12 +217,15 @@
                 final float x1 = getRawX(event, 1);
                 final float y1 = getRawY(event, 1);
 
-                boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop ||
-                x1 < edgeSlop || y1 < edgeSlop;
-                boolean p1sloppy = x0 > rightSlop || y0 > bottomSlop ||
-                x1 > rightSlop || y1 > bottomSlop;
+                boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop
+                        || x0 > rightSlop || y0 > bottomSlop;
+                boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop
+                        || x1 > rightSlop || y1 > bottomSlop;
 
-                if (p0sloppy) {
+                if(p0sloppy && p1sloppy) {
+                    mFocusX = -1;
+                    mFocusY = -1;
+                } else if (p0sloppy) {
                     mFocusX = event.getX(1);
                     mFocusY = event.getY(1);
                 } else if (p1sloppy) {
@@ -226,6 +235,14 @@
                     mSloppyGesture = false;
                     mGestureInProgress = mListener.onScaleBegin(this);
                 }
+            } else if ((action == MotionEvent.ACTION_POINTER_1_UP
+                    || action == MotionEvent.ACTION_POINTER_2_UP)
+                    && mSloppyGesture) {
+                // Set focus point to the remaining finger
+                int id = (((action & MotionEvent.ACTION_POINTER_ID_MASK)
+                        >> MotionEvent.ACTION_POINTER_ID_SHIFT) == 0) ? 1 : 0;
+                mFocusX = event.getX(id);
+                mFocusY = event.getY(id);
             }
         } else {
             // Transform gesture in progress - attempt to handle it
diff --git a/core/java/android/webkit/SslErrorHandler.java b/core/java/android/webkit/SslErrorHandler.java
index 90ed65d..d99c2c0 100644
--- a/core/java/android/webkit/SslErrorHandler.java
+++ b/core/java/android/webkit/SslErrorHandler.java
@@ -51,6 +51,11 @@
      */
     private Bundle mSslPrefTable;
 
+    /**
+     * Flag indicating that a client reponse is pending.
+     */
+    private boolean mResponsePending;
+
     // Message id for handling the response
     private static final int HANDLE_RESPONSE = 100;
 
@@ -191,6 +196,7 @@
             // if we do not have information on record, ask
             // the user (display a dialog)
             CallbackProxy proxy = loader.getFrame().getCallbackProxy();
+            mResponsePending = true;
             proxy.onReceivedSslError(this, error);
         }
 
@@ -202,7 +208,11 @@
      * Proceed with the SSL certificate.
      */
     public void proceed() {
-        sendMessage(obtainMessage(HANDLE_RESPONSE, 1, 0, mLoaderQueue.poll()));
+        if (mResponsePending) {
+            mResponsePending = false;
+            sendMessage(obtainMessage(HANDLE_RESPONSE, 1, 0,
+                        mLoaderQueue.poll()));
+        }
     }
 
     /**
@@ -210,7 +220,11 @@
      * the error.
      */
     public void cancel() {
-        sendMessage(obtainMessage(HANDLE_RESPONSE, 0, 0, mLoaderQueue.poll()));
+        if (mResponsePending) {
+            mResponsePending = false;
+            sendMessage(obtainMessage(HANDLE_RESPONSE, 0, 0,
+                        mLoaderQueue.poll()));
+        }
     }
 
     /**
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 56650a6..93e72ff 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -4607,14 +4607,11 @@
                                 break;
                             }
                         } else {
-                            if (mPreventDrag == PREVENT_DRAG_MAYBE_YES) {
-                                // if mPreventDrag is not confirmed, treat it as
-                                // no so that it won't block tap or double tap.
-                                mPreventDrag = PREVENT_DRAG_NO;
-                                mPreventLongPress = false;
-                                mPreventDoubleTap = false;
-                            }
-                            if (mPreventDrag == PREVENT_DRAG_NO) {
+                            // mPreventDrag can be PREVENT_DRAG_MAYBE_YES in
+                            // TOUCH_INIT_MODE. To give WebCoreThread a little
+                            // more time to send PREVENT_TOUCH_ID, we check
+                            // again in responding RELEASE_SINGLE_TAP.
+                            if (mPreventDrag != PREVENT_DRAG_YES) {
                                 if (mTouchMode == TOUCH_INIT_MODE) {
                                     mPrivateHandler.sendMessageDelayed(
                                             mPrivateHandler.obtainMessage(
@@ -5631,6 +5628,13 @@
                     break;
                 }
                 case RELEASE_SINGLE_TAP: {
+                    if (mPreventDrag == PREVENT_DRAG_MAYBE_YES) {
+                        // if mPreventDrag is not confirmed, treat it as
+                        // no so that it won't block tap.
+                        mPreventDrag = PREVENT_DRAG_NO;
+                        mPreventLongPress = false;
+                        mPreventDoubleTap = false;
+                    }
                     if (mPreventDrag == PREVENT_DRAG_NO) {
                         mTouchMode = TOUCH_DONE_MODE;
                         doShortPress();
@@ -5863,7 +5867,7 @@
                         // updates is a C++ pointer to a Vector of
                         // AnimationValues that we apply to the layers.
                         // The Vector is deallocated in nativeUpdateLayers().
-                        nativeUpdateLayers(mRootLayer, updates);
+                        nativeUpdateLayers(updates);
                     }
                     invalidate();
                     break;
@@ -5985,10 +5989,22 @@
                         }
                         mFullScreenHolder = new PluginFullScreenHolder(
                                 WebView.this, data.mNpp);
+                        // as we are sharing the View between full screen and
+                        // embedded mode, we have to remove the
+                        // AbsoluteLayout.LayoutParams set by embedded mode to
+                        // ViewGroup.LayoutParams before adding it to the dialog
+                        data.mView.setLayoutParams(new ViewGroup.LayoutParams(
+                                ViewGroup.LayoutParams.FILL_PARENT,
+                                ViewGroup.LayoutParams.FILL_PARENT));
                         mFullScreenHolder.setContentView(data.mView);
                         mFullScreenHolder.setCancelable(false);
                         mFullScreenHolder.setCanceledOnTouchOutside(false);
                         mFullScreenHolder.show();
+                    } else if (mFullScreenHolder == null) {
+                        // this may happen if user dismisses the fullscreen and
+                        // then the WebCore re-position message finally reached
+                        // the UI thread.
+                        break;
                     }
                     // move the matching embedded view fully into the view so
                     // that touch will be valid instead of rejected due to out
@@ -6602,7 +6618,7 @@
     private native void     nativeDestroyLayer(int layer);
     private native int      nativeEvaluateLayersAnimations(int layer);
     private native boolean  nativeLayersHaveAnimations(int layer);
-    private native void     nativeUpdateLayers(int layer, int updates);
+    private native void     nativeUpdateLayers(int updates);
     private native void     nativeDrawLayers(int layer,
                                              int scrollX, int scrollY,
                                              int width, int height,
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 3d8d296..3995026 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -64,10 +64,10 @@
 }
 
 static jboolean
-android_media_AudioSystem_isMusicActive(JNIEnv *env, jobject thiz)
+android_media_AudioSystem_isStreamActive(JNIEnv *env, jobject thiz, jint stream)
 {
     bool state = false;
-    AudioSystem::isMusicActive(&state);
+    AudioSystem::isStreamActive(stream, &state);
     return state;
 }
 
@@ -195,7 +195,7 @@
     {"getParameters",        "(Ljava/lang/String;)Ljava/lang/String;", (void *)android_media_AudioSystem_getParameters},
     {"muteMicrophone",      "(Z)I",     (void *)android_media_AudioSystem_muteMicrophone},
     {"isMicrophoneMuted",   "()Z",      (void *)android_media_AudioSystem_isMicrophoneMuted},
-    {"isMusicActive",       "()Z",      (void *)android_media_AudioSystem_isMusicActive},
+    {"isStreamActive",      "(I)Z",     (void *)android_media_AudioSystem_isStreamActive},
     {"setDeviceConnectionState", "(IILjava/lang/String;)I", (void *)android_media_AudioSystem_setDeviceConnectionState},
     {"getDeviceConnectionState", "(ILjava/lang/String;)I",  (void *)android_media_AudioSystem_getDeviceConnectionState},
     {"setPhoneState",       "(I)I",     (void *)android_media_AudioSystem_setPhoneState},
diff --git a/docs/html/guide/developing/debug-tasks.jd b/docs/html/guide/developing/debug-tasks.jd
index 975f6998..a980efc 100644
--- a/docs/html/guide/developing/debug-tasks.jd
+++ b/docs/html/guide/developing/debug-tasks.jd
@@ -80,13 +80,25 @@
 
 <h2 id="additionaldebugging">Debugging and Testing with Dev Tools</h2>
 
-<p>With the Dev Tools application, you can turn on a number of settings that will
-make it easier to test and debug your applications. The Dev Tools application is automatically
-installed on all system images included with the SDK. The source code for the Dev Tools application
-is also provided in the SDK samples so that you may build it and then install the application on any
-development device.</p>
+<p>With the Dev Tools application, you can enable a number of settings on your device that will
+make it easier to test and debug your applications.</p>
 
-<p>To get to the development settings page on the emulator, launch the Dev Tools application and
+<p>The Dev Tools application is installed by default
+on all system images included with the SDK, so you can use it with the Android Emulator. If you'd
+like to install the Dev Tools application on a real development device, you can copy the
+application from your emulator and then install it on your device using ADB. To copy the
+application from a running emulator, execute:
+</p>
+<pre>
+adb -e pull /system/app/Development.apk ./Development.apk
+</pre>
+<p>This copies the .apk file into the current directory. Then install it on your connected device
+with:</p>
+<pre>
+adb -d install Development.apk
+</pre>
+
+<p>To get started, launch the Dev Tools application and
 select Development Settings. This will open the Development Settings page with the
 following options (among others):</p>
 
@@ -132,7 +144,7 @@
         can happen during debugging.</dd>
 </dl>
 
-<p>These settings will be remembered across emulator restarts. </p>
+<p>These settings will be remembered across emulator restarts.</p>
 
 <h2 id="DebuggingWebPages">Debugging Web Pages</h2>
 
diff --git a/include/media/AudioSystem.h b/include/media/AudioSystem.h
index f935bb9..b42bf54 100644
--- a/include/media/AudioSystem.h
+++ b/include/media/AudioSystem.h
@@ -194,8 +194,8 @@
     // set audio mode in audio hardware (see AudioSystem::audio_mode)
     static status_t setMode(int mode);
 
-    // returns true if tracks are active on AudioSystem::MUSIC stream
-    static status_t isMusicActive(bool *state);
+    // returns true in *state if tracks are active on the specified stream
+    static status_t isStreamActive(int stream, bool *state);
 
     // set/get audio hardware parameters. The function accepts a list of parameters
     // key value pairs in the form: key1=value1;key2=value2;...
diff --git a/include/media/IAudioFlinger.h b/include/media/IAudioFlinger.h
index a46c727..b689dcbc 100644
--- a/include/media/IAudioFlinger.h
+++ b/include/media/IAudioFlinger.h
@@ -97,8 +97,8 @@
     virtual     status_t    setMicMute(bool state) = 0;
     virtual     bool        getMicMute() const = 0;
 
-    // is a music stream active?
-    virtual     bool        isMusicActive() const = 0;
+    // is any track active on this stream?
+    virtual     bool        isStreamActive(int stream) const = 0;
 
     virtual     status_t    setParameters(int ioHandle, const String8& keyValuePairs) = 0;
     virtual     String8     getParameters(int ioHandle, const String8& keys) = 0;
diff --git a/include/media/stagefright/AMRWriter.h b/include/media/stagefright/AMRWriter.h
new file mode 100644
index 0000000..372909a
--- /dev/null
+++ b/include/media/stagefright/AMRWriter.h
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+#ifndef AMR_WRITER_H_
+
+#define AMR_WRITER_H_
+
+#include <stdio.h>
+
+#include <media/stagefright/MediaWriter.h>
+#include <utils/threads.h>
+
+namespace android {
+
+struct MediaSource;
+
+struct AMRWriter : public MediaWriter {
+    AMRWriter(const char *filename);
+    AMRWriter(int fd);
+
+    status_t initCheck() const;
+
+    virtual status_t addSource(const sp<MediaSource> &source);
+    virtual bool reachedEOS();
+    virtual status_t start();
+    virtual void stop();
+
+protected:
+    virtual ~AMRWriter();
+
+private:
+    Mutex mLock;
+
+    FILE *mFile;
+    status_t mInitCheck;
+    sp<MediaSource> mSource;
+    bool mStarted;
+    volatile bool mDone;
+    bool mReachedEOS;
+    pthread_t mThread;
+
+    static void *ThreadWrapper(void *);
+    void threadFunc();
+
+    AMRWriter(const AMRWriter &);
+    AMRWriter &operator=(const AMRWriter &);
+};
+
+}  // namespace android
+
+#endif  // AMR_WRITER_H_
diff --git a/include/media/stagefright/AudioSource.h b/include/media/stagefright/AudioSource.h
index e129958..eb00140 100644
--- a/include/media/stagefright/AudioSource.h
+++ b/include/media/stagefright/AudioSource.h
@@ -18,16 +18,20 @@
 
 #define AUDIO_SOURCE_H_
 
+#include <media/AudioSystem.h>
 #include <media/stagefright/MediaSource.h>
 
 namespace android {
 
 class AudioRecord;
+struct MediaBufferGroup;
 
-class AudioSource {
-public:
-    AudioSource(int inputSource);
-    virtual ~AudioSource();
+struct AudioSource : public MediaSource {
+    // Note that the "channels" parameter is _not_ the number of channels,
+    // but a bitmask of AudioSystem::audio_channels constants.
+    AudioSource(
+            int inputSource, uint32_t sampleRate,
+            uint32_t channels = AudioSystem::CHANNEL_IN_MONO);
 
     status_t initCheck() const;
 
@@ -38,9 +42,16 @@
     virtual status_t read(
             MediaBuffer **buffer, const ReadOptions *options = NULL);
 
+protected:
+    virtual ~AudioSource();
+
 private:
+    enum { kMaxBufferSize = 8192 };
+
     AudioRecord *mRecord;
     status_t mInitCheck;
+    bool mStarted;
+    MediaBufferGroup *mGroup;
 
     AudioSource(const AudioSource &);
     AudioSource &operator=(const AudioSource &);
diff --git a/include/media/stagefright/MPEG4Writer.h b/include/media/stagefright/MPEG4Writer.h
index 2ca04fa..6b93f19 100644
--- a/include/media/stagefright/MPEG4Writer.h
+++ b/include/media/stagefright/MPEG4Writer.h
@@ -20,8 +20,8 @@
 
 #include <stdio.h>
 
+#include <media/stagefright/MediaWriter.h>
 #include <utils/List.h>
-#include <utils/RefBase.h>
 #include <utils/threads.h>
 
 namespace android {
@@ -30,15 +30,15 @@
 class MediaSource;
 class MetaData;
 
-class MPEG4Writer : public RefBase {
+class MPEG4Writer : public MediaWriter {
 public:
     MPEG4Writer(const char *filename);
     MPEG4Writer(int fd);
 
-    void addSource(const sp<MediaSource> &source);
-    status_t start();
-    bool reachedEOS();
-    void stop();
+    virtual status_t addSource(const sp<MediaSource> &source);
+    virtual status_t start();
+    virtual bool reachedEOS();
+    virtual void stop();
 
     void beginBox(const char *fourcc);
     void writeInt8(int8_t x);
diff --git a/include/media/stagefright/MediaWriter.h b/include/media/stagefright/MediaWriter.h
new file mode 100644
index 0000000..b8232c6
--- /dev/null
+++ b/include/media/stagefright/MediaWriter.h
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+#ifndef MEDIA_WRITER_H_
+
+#define MEDIA_WRITER_H_
+
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct MediaSource;
+
+struct MediaWriter : public RefBase {
+    MediaWriter() {}
+
+    virtual status_t addSource(const sp<MediaSource> &source) = 0;
+    virtual bool reachedEOS() = 0;
+    virtual status_t start() = 0;
+    virtual void stop() = 0;
+
+protected:
+    virtual ~MediaWriter() {}
+
+private:
+    MediaWriter(const MediaWriter &);
+    MediaWriter &operator=(const MediaWriter &);
+};
+
+}  // namespace android
+
+#endif  // MEDIA_WRITER_H_
diff --git a/libs/audioflinger/AudioFlinger.cpp b/libs/audioflinger/AudioFlinger.cpp
index ecfe1e0..5c58603 100644
--- a/libs/audioflinger/AudioFlinger.cpp
+++ b/libs/audioflinger/AudioFlinger.cpp
@@ -544,11 +544,11 @@
     return mStreamTypes[stream].mute;
 }
 
-bool AudioFlinger::isMusicActive() const
+bool AudioFlinger::isStreamActive(int stream) const
 {
     Mutex::Autolock _l(mLock);
     for (uint32_t i = 0; i < mPlaybackThreads.size(); i++) {
-        if (mPlaybackThreads.valueAt(i)->isMusicActive()) {
+        if (mPlaybackThreads.valueAt(i)->isStreamActive(stream)) {
             return true;
         }
     }
@@ -1071,7 +1071,7 @@
     return mStreamTypes[stream].mute;
 }
 
-bool AudioFlinger::PlaybackThread::isMusicActive() const
+bool AudioFlinger::PlaybackThread::isStreamActive(int stream) const
 {
     Mutex::Autolock _l(mLock);
     size_t count = mActiveTracks.size();
@@ -1079,7 +1079,7 @@
         sp<Track> t = mActiveTracks[i].promote();
         if (t == 0) continue;
         Track* const track = t.get();
-        if (t->type() == AudioSystem::MUSIC)
+        if (t->type() == stream)
             return true;
     }
     return false;
diff --git a/libs/audioflinger/AudioFlinger.h b/libs/audioflinger/AudioFlinger.h
index 12c90eb..52999b1 100644
--- a/libs/audioflinger/AudioFlinger.h
+++ b/libs/audioflinger/AudioFlinger.h
@@ -100,7 +100,7 @@
     virtual     status_t    setMicMute(bool state);
     virtual     bool        getMicMute() const;
 
-    virtual     bool        isMusicActive() const;
+    virtual     bool        isStreamActive(int stream) const;
 
     virtual     status_t    setParameters(int ioHandle, const String8& keyValuePairs);
     virtual     String8     getParameters(int ioHandle, const String8& keys);
@@ -506,7 +506,7 @@
         virtual     float       streamVolume(int stream) const;
         virtual     bool        streamMute(int stream) const;
 
-                    bool        isMusicActive() const;
+                    bool        isStreamActive(int stream) const;
 
                     sp<Track>   createTrack_l(
                                     const sp<AudioFlinger::Client>& client,
diff --git a/libs/ui/EventHub.cpp b/libs/ui/EventHub.cpp
index e39a357..c4d4f99 100644
--- a/libs/ui/EventHub.cpp
+++ b/libs/ui/EventHub.cpp
@@ -500,7 +500,7 @@
     LOGV("Opening device: %s", deviceName);
 
     AutoMutex _l(mLock);
-    
+
     fd = open(deviceName, O_RDWR);
     if(fd < 0) {
         LOGE("could not open %s, %s\n", deviceName, strerror(errno));
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index bb16215a..171881f 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -797,7 +797,7 @@
     public void setMode(int mode) {
         IAudioService service = getService();
         try {
-            service.setMode(mode);
+            service.setMode(mode, mICallBack);
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in setMode", e);
         }
@@ -924,7 +924,7 @@
      * @return true if any music tracks are active.
      */
     public boolean isMusicActive() {
-        return AudioSystem.isMusicActive();
+        return AudioSystem.isStreamActive(STREAM_MUSIC);
     }
 
     /*
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 482fc4f..bde8a47 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -54,7 +54,6 @@
 import java.util.Map;
 import java.util.Set;
 
-
 /**
  * The implementation of the volume manager service.
  * <p>
@@ -231,6 +230,9 @@
     // Forced device usage for communications
     private int mForcedUseForComm;
 
+    // List of binder death handlers for setMode() client processes.
+    // The last process to have called setMode() is at the top of the list.
+    private ArrayList <SetModeDeathHandler> mSetModeDeathHandlers = new ArrayList <SetModeDeathHandler>();
 
     ///////////////////////////////////////////////////////////////////////////
     // Construction
@@ -248,11 +250,13 @@
 
         mVolumePanel = new VolumePanel(context, this);
         mSettingsObserver = new SettingsObserver();
-        mMode = AudioSystem.MODE_NORMAL;
         mForcedUseForComm = AudioSystem.FORCE_NONE;
         createAudioSystemThread();
         readPersistedSettings();
         createStreamStates();
+        // Call setMode() to initialize mSetModeDeathHandlers
+        mMode = AudioSystem.MODE_INVALID;
+        setMode(AudioSystem.MODE_NORMAL, null);
         mMediaServerOk = true;
         AudioSystem.setErrorCallback(mAudioSystemCallback);
         loadSoundEffects();
@@ -582,8 +586,54 @@
         return existingValue;
     }
 
+    private class SetModeDeathHandler implements IBinder.DeathRecipient {
+        private IBinder mCb; // To be notified of client's death
+        private int mMode = AudioSystem.MODE_NORMAL; // Current mode set by this client
+
+        SetModeDeathHandler(IBinder cb) {
+            mCb = cb;
+        }
+
+        public void binderDied() {
+            synchronized(mSetModeDeathHandlers) {
+                Log.w(TAG, "setMode() client died");
+                int index = mSetModeDeathHandlers.indexOf(this);
+                if (index < 0) {
+                    Log.w(TAG, "unregistered setMode() client died");
+                } else {
+                    mSetModeDeathHandlers.remove(this);
+                    // If dead client was a the top of client list,
+                    // apply next mode in the stack
+                    if (index == 0) {
+                        // mSetModeDeathHandlers is never empty as the initial entry
+                        // created when AudioService starts is never removed
+                        SetModeDeathHandler hdlr = mSetModeDeathHandlers.get(0);
+                        int mode = hdlr.getMode();
+                        if (AudioService.this.mMode != mode) {
+                            if (AudioSystem.setPhoneState(mode) == AudioSystem.AUDIO_STATUS_OK) {
+                                AudioService.this.mMode = mode;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        public void setMode(int mode) {
+            mMode = mode;
+        }
+
+        public int getMode() {
+            return mMode;
+        }
+
+        public IBinder getBinder() {
+            return mCb;
+        }
+    }
+
     /** @see AudioManager#setMode(int) */
-    public void setMode(int mode) {
+    public void setMode(int mode, IBinder cb) {
         if (!checkAudioSettingsPermission("setMode()")) {
             return;
         }
@@ -599,6 +649,37 @@
             if (mode != mMode) {
                 if (AudioSystem.setPhoneState(mode) == AudioSystem.AUDIO_STATUS_OK) {
                     mMode = mode;
+
+                    synchronized(mSetModeDeathHandlers) {
+                        SetModeDeathHandler hdlr = null;
+                        Iterator iter = mSetModeDeathHandlers.iterator();
+                        while (iter.hasNext()) {
+                            SetModeDeathHandler h = (SetModeDeathHandler)iter.next();
+                            if (h.getBinder() == cb) {
+                                hdlr = h;
+                                // Remove from client list so that it is re-inserted at top of list
+                                iter.remove();
+                                break;
+                            }
+                        }
+                        if (hdlr == null) {
+                            hdlr = new SetModeDeathHandler(cb);
+                            // cb is null when setMode() is called by AudioService constructor
+                            if (cb != null) {
+                                // Register for client death notification
+                                try {
+                                    cb.linkToDeath(hdlr, 0);
+                                } catch (RemoteException e) {
+                                    // Client has died!
+                                    Log.w(TAG, "setMode() could not link to "+cb+" binder death");
+                                }
+                            }
+                        }
+                        // Last client to call setMode() is always at top of client list
+                        // as required by SetModeDeathHandler.binderDied()
+                        mSetModeDeathHandlers.add(0, hdlr);
+                        hdlr.setMode(mode);
+                    }
                 }
             }
             int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
@@ -876,10 +957,10 @@
         if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION) == AudioSystem.FORCE_BT_SCO) {
             // Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO...");
             return AudioSystem.STREAM_BLUETOOTH_SCO;
-        } else if (isOffhook) {
+        } else if (isOffhook || AudioSystem.isStreamActive(AudioSystem.STREAM_VOICE_CALL)) {
             // Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL...");
             return AudioSystem.STREAM_VOICE_CALL;
-        } else if (AudioSystem.isMusicActive()) {
+        } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC)) {
             // Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC...");
             return AudioSystem.STREAM_MUSIC;
         } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
@@ -1285,7 +1366,7 @@
                     // Force creation of new IAudioflinger interface
                     if (!mMediaServerOk) {
                         Log.e(TAG, "Media server died.");
-                        AudioSystem.isMusicActive();
+                        AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC);
                         sendMsg(mAudioHandler, MSG_MEDIA_SERVER_DIED, SHARED_MSG, SENDMSG_NOOP, 0, 0,
                                 null, 500);
                     }
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 137b919..a4818ff 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -153,11 +153,11 @@
     }
 
     /*
-     * Checks whether any music is active.
+     * Checks whether the specified stream type is active.
      *
-     * return true if any music tracks are active.
+     * return true if any track playing on this stream is active.
      */
-    public static native boolean isMusicActive();
+    public static native boolean isStreamActive(int stream);
 
     /*
      * Sets a group generic audio configuration parameters. The use of these parameters
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index d3d2d29..83581d2 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -47,7 +47,7 @@
     
     boolean shouldVibrate(int vibrateType);
 
-    void setMode(int mode);
+    void setMode(int mode, IBinder cb);
 
     int getMode();
 
diff --git a/media/libmedia/AudioSystem.cpp b/media/libmedia/AudioSystem.cpp
index e1b1776..e3b829b 100644
--- a/media/libmedia/AudioSystem.cpp
+++ b/media/libmedia/AudioSystem.cpp
@@ -170,10 +170,10 @@
 }
 
 
-status_t AudioSystem::isMusicActive(bool* state) {
+status_t AudioSystem::isStreamActive(int stream, bool* state) {
     const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger();
     if (af == 0) return PERMISSION_DENIED;
-    *state = af->isMusicActive();
+    *state = af->isStreamActive(stream);
     return NO_ERROR;
 }
 
diff --git a/media/libmedia/IAudioFlinger.cpp b/media/libmedia/IAudioFlinger.cpp
index 0eff205..fc42979 100644
--- a/media/libmedia/IAudioFlinger.cpp
+++ b/media/libmedia/IAudioFlinger.cpp
@@ -47,7 +47,7 @@
     SET_MODE,
     SET_MIC_MUTE,
     GET_MIC_MUTE,
-    IS_MUSIC_ACTIVE,
+    IS_STREAM_ACTIVE,
     SET_PARAMETERS,
     GET_PARAMETERS,
     REGISTER_CLIENT,
@@ -286,11 +286,12 @@
         return reply.readInt32();
     }
 
-    virtual bool isMusicActive() const
+    virtual bool isStreamActive(int stream) const
     {
         Parcel data, reply;
         data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor());
-        remote()->transact(IS_MUSIC_ACTIVE, data, &reply);
+        data.writeInt32(stream);
+        remote()->transact(IS_STREAM_ACTIVE, data, &reply);
         return reply.readInt32();
     }
 
@@ -599,9 +600,10 @@
             reply->writeInt32( getMicMute() );
             return NO_ERROR;
         } break;
-        case IS_MUSIC_ACTIVE: {
+        case IS_STREAM_ACTIVE: {
             CHECK_INTERFACE(IAudioFlinger, data, reply);
-            reply->writeInt32( isMusicActive() );
+            int stream = data.readInt32();
+            reply->writeInt32( isStreamActive(stream) );
             return NO_ERROR;
         } break;
         case SET_PARAMETERS: {
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index a55273d..6383f0c 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -20,6 +20,8 @@
 
 #include "StagefrightRecorder.h"
 
+#include <media/stagefright/AudioSource.h>
+#include <media/stagefright/AMRWriter.h>
 #include <media/stagefright/CameraSource.h>
 #include <media/stagefright/MPEG4Writer.h>
 #include <media/stagefright/MediaDebug.h>
@@ -146,7 +148,90 @@
         return UNKNOWN_ERROR;
     }
 
-    if (mVideoSource == VIDEO_SOURCE_CAMERA) {
+    switch (mOutputFormat) {
+        case OUTPUT_FORMAT_DEFAULT:
+        case OUTPUT_FORMAT_THREE_GPP:
+        case OUTPUT_FORMAT_MPEG_4:
+            return startMPEG4Recording();
+
+        case OUTPUT_FORMAT_AMR_NB:
+        case OUTPUT_FORMAT_AMR_WB:
+            return startAMRRecording();
+
+        default:
+            return UNKNOWN_ERROR;
+    }
+}
+
+sp<MediaSource> StagefrightRecorder::createAMRAudioSource() {
+    uint32_t sampleRate =
+        mAudioEncoder == AUDIO_ENCODER_AMR_NB ? 8000 : 16000;
+
+    sp<AudioSource> audioSource =
+        new AudioSource(
+                mAudioSource,
+                sampleRate,
+                AudioSystem::CHANNEL_IN_MONO);
+
+    status_t err = audioSource->initCheck();
+
+    if (err != OK) {
+        return NULL;
+    }
+
+    sp<MetaData> encMeta = new MetaData;
+    encMeta->setCString(
+            kKeyMIMEType,
+            mAudioEncoder == AUDIO_ENCODER_AMR_NB
+                ? MEDIA_MIMETYPE_AUDIO_AMR_NB : MEDIA_MIMETYPE_AUDIO_AMR_WB);
+
+    encMeta->setInt32(kKeyChannelCount, 1);
+    encMeta->setInt32(kKeySampleRate, sampleRate);
+
+    OMXClient client;
+    CHECK_EQ(client.connect(), OK);
+
+    sp<MediaSource> audioEncoder =
+        OMXCodec::Create(client.interface(), encMeta,
+                         true /* createEncoder */, audioSource);
+
+    return audioEncoder;
+}
+
+status_t StagefrightRecorder::startAMRRecording() {
+    if (mAudioSource == AUDIO_SOURCE_LIST_END
+        || mVideoSource != VIDEO_SOURCE_LIST_END) {
+        return UNKNOWN_ERROR;
+    }
+
+    if (mOutputFormat == OUTPUT_FORMAT_AMR_NB
+            && mAudioEncoder != AUDIO_ENCODER_DEFAULT
+            && mAudioEncoder != AUDIO_ENCODER_AMR_NB) {
+        return UNKNOWN_ERROR;
+    } else if (mOutputFormat == OUTPUT_FORMAT_AMR_WB
+            && mAudioEncoder != AUDIO_ENCODER_AMR_WB) {
+        return UNKNOWN_ERROR;
+    }
+
+    sp<MediaSource> audioEncoder = createAMRAudioSource();
+
+    if (audioEncoder == NULL) {
+        return UNKNOWN_ERROR;
+    }
+
+    CHECK(mOutputFd >= 0);
+    mWriter = new AMRWriter(dup(mOutputFd));
+    mWriter->addSource(audioEncoder);
+    mWriter->start();
+
+    return OK;
+}
+
+status_t StagefrightRecorder::startMPEG4Recording() {
+    mWriter = new MPEG4Writer(dup(mOutputFd));
+
+    if (mVideoSource == VIDEO_SOURCE_DEFAULT
+            || mVideoSource == VIDEO_SOURCE_CAMERA) {
         CHECK(mCamera != NULL);
 
         sp<CameraSource> cameraSource =
@@ -193,11 +278,20 @@
                     true /* createEncoder */, cameraSource);
 
         CHECK(mOutputFd >= 0);
-        mWriter = new MPEG4Writer(dup(mOutputFd));
         mWriter->addSource(encoder);
-        mWriter->start();
     }
 
+    if (mAudioSource != AUDIO_SOURCE_LIST_END) {
+        sp<MediaSource> audioEncoder = createAMRAudioSource();
+
+        if (audioEncoder == NULL) {
+            return UNKNOWN_ERROR;
+        }
+
+        mWriter->addSource(audioEncoder);
+    }
+
+    mWriter->start();
     return OK;
 }
 
@@ -235,7 +329,9 @@
 }
 
 status_t StagefrightRecorder::getMaxAmplitude(int *max) {
-    return UNKNOWN_ERROR;
+    *max = 0;
+
+    return OK;
 }
 
 }  // namespace android
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index 56c4e0e..7ec412d 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -23,7 +23,8 @@
 
 namespace android {
 
-class MPEG4Writer;
+struct MediaSource;
+struct MediaWriter;
 
 struct StagefrightRecorder : public MediaRecorderBase {
     StagefrightRecorder();
@@ -54,7 +55,7 @@
     sp<ICamera> mCamera;
     sp<ISurface> mPreviewSurface;
     sp<IMediaPlayerClient> mListener;
-    sp<MPEG4Writer> mWriter;
+    sp<MediaWriter> mWriter;
 
     audio_source mAudioSource;
     video_source mVideoSource;
@@ -66,6 +67,10 @@
     String8 mParams;
     int mOutputFd;
 
+    status_t startMPEG4Recording();
+    status_t startAMRRecording();
+    sp<MediaSource> createAMRAudioSource();
+
     StagefrightRecorder(const StagefrightRecorder &);
     StagefrightRecorder &operator=(const StagefrightRecorder &);
 };
diff --git a/media/libstagefright/AMRWriter.cpp b/media/libstagefright/AMRWriter.cpp
new file mode 100644
index 0000000..caff452
--- /dev/null
+++ b/media/libstagefright/AMRWriter.cpp
@@ -0,0 +1,193 @@
+/*
+ * 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.
+ */
+
+#include <media/stagefright/AMRWriter.h>
+
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+
+namespace android {
+
+AMRWriter::AMRWriter(const char *filename)
+    : mFile(fopen(filename, "wb")),
+      mInitCheck(mFile != NULL ? OK : NO_INIT),
+      mStarted(false) {
+}
+
+AMRWriter::AMRWriter(int fd)
+    : mFile(fdopen(fd, "wb")),
+      mInitCheck(mFile != NULL ? OK : NO_INIT),
+      mStarted(false) {
+}
+
+AMRWriter::~AMRWriter() {
+    if (mStarted) {
+        stop();
+    }
+
+    if (mFile != NULL) {
+        fclose(mFile);
+        mFile = NULL;
+    }
+}
+
+status_t AMRWriter::initCheck() const {
+    return mInitCheck;
+}
+
+status_t AMRWriter::addSource(const sp<MediaSource> &source) {
+    Mutex::Autolock autoLock(mLock);
+
+    if (mInitCheck != OK) {
+        return mInitCheck;
+    }
+
+    if (mSource != NULL) {
+        // AMR files only support a single track of audio.
+        return UNKNOWN_ERROR;
+    }
+
+    sp<MetaData> meta = source->getFormat();
+
+    const char *mime;
+    CHECK(meta->findCString(kKeyMIMEType, &mime));
+
+    bool isWide = false;
+    if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {
+        isWide = true;
+    } else if (strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)) {
+        return ERROR_UNSUPPORTED;
+    }
+
+    int32_t channelCount;
+    int32_t sampleRate;
+    CHECK(meta->findInt32(kKeyChannelCount, &channelCount));
+    CHECK_EQ(channelCount, 1);
+    CHECK(meta->findInt32(kKeySampleRate, &sampleRate));
+    CHECK_EQ(sampleRate, (isWide ? 16000 : 8000));
+
+    mSource = source;
+
+    const char *kHeader = isWide ? "#!AMR-WB\n" : "#!AMR\n";
+    size_t n = strlen(kHeader);
+    if (fwrite(kHeader, 1, n, mFile) != n) {
+        return ERROR_IO;
+    }
+
+    return OK;
+}
+
+status_t AMRWriter::start() {
+    Mutex::Autolock autoLock(mLock);
+
+    if (mInitCheck != OK) {
+        return mInitCheck;
+    }
+
+    if (mStarted || mSource == NULL) {
+        return UNKNOWN_ERROR;
+    }
+
+    status_t err = mSource->start();
+
+    if (err != OK) {
+        return err;
+    }
+
+    pthread_attr_t attr;
+    pthread_attr_init(&attr);
+    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+
+    mReachedEOS = false;
+    mDone = false;
+
+    pthread_create(&mThread, &attr, ThreadWrapper, this);
+    pthread_attr_destroy(&attr);
+
+    mStarted = true;
+
+    return OK;
+}
+
+void AMRWriter::stop() {
+    {
+        Mutex::Autolock autoLock(mLock);
+
+        if (!mStarted) {
+            return;
+        }
+
+        mDone = true;
+    }
+
+    void *dummy;
+    pthread_join(mThread, &dummy);
+
+    mSource->stop();
+
+    mStarted = false;
+}
+
+// static
+void *AMRWriter::ThreadWrapper(void *me) {
+    static_cast<AMRWriter *>(me)->threadFunc();
+
+    return NULL;
+}
+
+void AMRWriter::threadFunc() {
+    for (;;) {
+        Mutex::Autolock autoLock(mLock);
+
+        if (mDone) {
+            break;
+        }
+
+        MediaBuffer *buffer;
+        status_t err = mSource->read(&buffer);
+
+        if (err != OK) {
+            break;
+        }
+
+        ssize_t n = fwrite(
+                (const uint8_t *)buffer->data() + buffer->range_offset(),
+                1,
+                buffer->range_length(),
+                mFile);
+
+        buffer->release();
+        buffer = NULL;
+
+        if (n < (ssize_t)buffer->range_length()) {
+            break;
+        }
+    }
+
+    Mutex::Autolock autoLock(mLock);
+    mReachedEOS = true;
+}
+
+bool AMRWriter::reachedEOS() {
+    Mutex::Autolock autoLock(mLock);
+    return mReachedEOS;
+}
+
+}  // namespace android
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 26b9357..3813907 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -16,7 +16,9 @@
 
 LOCAL_SRC_FILES +=                \
         AMRExtractor.cpp          \
+        AMRWriter.cpp             \
         AudioPlayer.cpp           \
+        AudioSource.cpp           \
         AwesomePlayer.cpp         \
         CachingDataSource.cpp     \
         CameraSource.cpp          \
diff --git a/media/libstagefright/AudioSource.cpp b/media/libstagefright/AudioSource.cpp
new file mode 100644
index 0000000..edabaf9
--- /dev/null
+++ b/media/libstagefright/AudioSource.cpp
@@ -0,0 +1,122 @@
+/*
+ * 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.
+ */
+
+#include <media/stagefright/AudioSource.h>
+
+#include <media/AudioRecord.h>
+#include <media/stagefright/MediaBufferGroup.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaData.h>
+
+namespace android {
+
+AudioSource::AudioSource(
+        int inputSource, uint32_t sampleRate, uint32_t channels)
+    : mRecord(new AudioRecord(
+                inputSource, sampleRate, AudioSystem::PCM_16_BIT, channels)),
+      mInitCheck(mRecord->initCheck()),
+      mStarted(false),
+      mGroup(NULL) {
+}
+
+AudioSource::~AudioSource() {
+    if (mStarted) {
+        stop();
+    }
+
+    delete mRecord;
+    mRecord = NULL;
+}
+
+status_t AudioSource::initCheck() const {
+    return mInitCheck;
+}
+
+status_t AudioSource::start(MetaData *params) {
+    if (mStarted) {
+        return UNKNOWN_ERROR;
+    }
+
+    status_t err = mRecord->start();
+
+    if (err == OK) {
+        mGroup = new MediaBufferGroup;
+        mGroup->add_buffer(new MediaBuffer(kMaxBufferSize));
+
+        mStarted = true;
+    }
+
+    return err;
+}
+
+status_t AudioSource::stop() {
+    if (!mStarted) {
+        return UNKNOWN_ERROR;
+    }
+
+    mRecord->stop();
+
+    delete mGroup;
+    mGroup = NULL;
+
+    mStarted = false;
+
+    return OK;
+}
+
+sp<MetaData> AudioSource::getFormat() {
+    sp<MetaData> meta = new MetaData;
+    meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW);
+    meta->setInt32(kKeySampleRate, mRecord->getSampleRate());
+    meta->setInt32(kKeyChannelCount, mRecord->channelCount());
+    meta->setInt32(kKeyMaxInputSize, kMaxBufferSize);
+
+    return meta;
+}
+
+status_t AudioSource::read(
+        MediaBuffer **out, const ReadOptions *options) {
+    *out = NULL;
+
+    MediaBuffer *buffer;
+    CHECK_EQ(mGroup->acquire_buffer(&buffer), OK);
+
+    uint32_t numFramesRecorded;
+    mRecord->getPosition(&numFramesRecorded);
+
+    buffer->meta_data()->setInt64(
+            kKeyTime,
+            (1000000ll * numFramesRecorded) / mRecord->getSampleRate()
+            - mRecord->latency() * 1000);
+
+    ssize_t n = mRecord->read(buffer->data(), buffer->size());
+
+    if (n < 0) {
+        buffer->release();
+        buffer = NULL;
+
+        return (status_t)n;
+    }
+
+    buffer->set_range(0, n);
+
+    *out = buffer;
+
+    return OK;
+}
+
+}  // namespace android
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index 367459f..aee4d15 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -92,9 +92,11 @@
     mTracks.clear();
 }
 
-void MPEG4Writer::addSource(const sp<MediaSource> &source) {
+status_t MPEG4Writer::addSource(const sp<MediaSource> &source) {
     Track *track = new Track(this, source);
     mTracks.push_back(track);
+
+    return OK;
 }
 
 status_t MPEG4Writer::start() {
diff --git a/media/sdutils/sdutil.cpp b/media/sdutils/sdutil.cpp
index 322f743..a61cccb 100644
--- a/media/sdutils/sdutil.cpp
+++ b/media/sdutils/sdutil.cpp
@@ -134,6 +134,12 @@
     gMountService->unmountSecureContainer(sId);
 }
 
+static void asec_rename(const char *oldId, const char *newId) {
+    String16 sOldId(oldId);
+    String16 sNewId(newId);
+    gMountService->renameSecureContainer(sOldId, sNewId);
+}
+
 static int asec_path(const char *id) {
     String16 sId(id);
     gMountService->getSecureContainerPath(sId);
@@ -212,7 +218,13 @@
         } else if (!strcmp(argument, "destroy")) {
             return android::asec_destroy(id);
         } else if (!strcmp(argument, "mount")) {
-            return android::asec_mount(id, argv[4], atoi(argv[5]));
+            if (argc == 6)
+                return android::asec_mount(id, argv[4], atoi(argv[5]));
+        } else if (!strcmp(argument, "rename")) {
+            if (argc == 5) {
+                android::asec_rename(id, argv[4]);
+                return 0;
+            }
         } else if (!strcmp(argument, "unmount")) {
             android::asec_unmount(id);
             return 0;
@@ -233,6 +245,7 @@
                     "    sdutil asec destroy <id>\n"
                     "    sdutil asec mount <id> <key> <ownerUid>\n"
                     "    sdutil asec unmount <id>\n"
+                    "    sdutil asec rename <oldId, newId>\n"
                     "    sdutil asec path <id>\n"
                     );
     return -1;
diff --git a/opengl/libagl/texture.cpp b/opengl/libagl/texture.cpp
index 13d078e..2875c13 100644
--- a/opengl/libagl/texture.cpp
+++ b/opengl/libagl/texture.cpp
@@ -583,7 +583,7 @@
 
 
 static __attribute__((noinline))
-void set_depth_and_fog(ogles_context_t* c, GLint z)
+void set_depth_and_fog(ogles_context_t* c, GGLfixed z)
 {
     const uint32_t enables = c->rasterizer.state.enables;
     // we need to compute Zw
@@ -592,8 +592,8 @@
     GGLfixed Zw;
     GGLfixed n = gglFloatToFixed(c->transforms.vpt.zNear);
     GGLfixed f = gglFloatToFixed(c->transforms.vpt.zFar);
-    if (z<=0)       Zw = n;
-    else if (z>=1)  Zw = f;
+    if (z<=0)               Zw = n;
+    else if (z>=0x10000)    Zw = f;
     else            Zw = gglMulAddx(z, (f-n), n);
     if (enables & GGL_ENABLE_FOG) {
         // set up fog if needed...
@@ -836,7 +836,7 @@
             c->rasterizer.procs.texCoord2i(c, s0, t0);
             const uint32_t enables = c->rasterizer.state.enables;
             if (ggl_unlikely(enables & (GGL_ENABLE_DEPTH_TEST|GGL_ENABLE_FOG)))
-                set_depth_and_fog(c, z);
+                set_depth_and_fog(c, gglIntToFixed(z));
 
             c->rasterizer.procs.color4xv(c, c->currentColorClamped.v);
             c->rasterizer.procs.disable(c, GGL_W_LERP);
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 5a8d35f..4417d7b 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -828,6 +828,28 @@
                     info.getExtraInfo());
         }
 
+        NetworkStateTracker newNet = tryFailover(prevNetType);
+        if (newNet != null) {
+            NetworkInfo switchTo = newNet.getNetworkInfo();
+            intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo);
+        } else {
+            intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
+        }
+        // do this before we broadcast the change
+        handleConnectivityChange();
+
+        sendStickyBroadcast(intent);
+        /*
+         * If the failover network is already connected, then immediately send
+         * out a followup broadcast indicating successful failover
+         */
+        if (newNet != null && newNet.getNetworkInfo().isConnected()) {
+            sendConnectedBroadcast(newNet.getNetworkInfo());
+        }
+    }
+
+    // returns -1 if no failover available
+    private NetworkStateTracker tryFailover(int prevNetType) {
         /*
          * If this is a default network, check if other defaults are available
          * or active
@@ -840,8 +862,7 @@
 
             int newType = -1;
             int newPriority = -1;
-            for (int checkType=0; checkType <=
-                    ConnectivityManager.MAX_NETWORK_TYPE; checkType++) {
+            for (int checkType=0; checkType <= ConnectivityManager.MAX_NETWORK_TYPE; checkType++) {
                 if (checkType == prevNetType) continue;
                 if (mNetAttributes[checkType] == null) continue;
                 if (mNetAttributes[checkType].isDefault()) {
@@ -884,29 +905,13 @@
                                     switchTo.getTypeName());
                         }
                     }
-                    intent.putExtra(ConnectivityManager.
-                            EXTRA_OTHER_NETWORK_INFO, switchTo);
                 } else {
-                    intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY,
-                            true);
                     newNet.reconnect();
                 }
-            } else {
-                intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY,
-                        true);
             }
         }
 
-        // do this before we broadcast the change
-        handleConnectivityChange();
-
-        sendStickyBroadcast(intent);
-        /*
-         * If the failover network is already connected, then immediately send
-         * out a followup broadcast indicating successful failover
-         */
-        if (newNet != null && newNet.getNetworkInfo().isConnected())
-            sendConnectedBroadcast(newNet.getNetworkInfo());
+        return newNet;
     }
 
     private void sendConnectedBroadcast(NetworkInfo info) {
@@ -964,7 +969,25 @@
             intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
             info.setFailover(false);
         }
+
+        NetworkStateTracker newNet = tryFailover(info.getType());
+        if (newNet != null) {
+            NetworkInfo switchTo = newNet.getNetworkInfo();
+            intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo);
+        } else {
+            intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
+        }
+        // do this before we broadcast the change
+        handleConnectivityChange();
+
         sendStickyBroadcast(intent);
+        /*
+         * If the failover network is already connected, then immediately send
+         * out a followup broadcast indicating successful failover
+         */
+        if (newNet != null && newNet.getNetworkInfo().isConnected()) {
+            sendConnectedBroadcast(newNet.getNetworkInfo());
+        }
     }
 
     private void sendStickyBroadcast(Intent intent) {
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 0cee31d..353da12 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -1056,6 +1056,11 @@
         mConnector.doCommand(cmd);
     }
 
+    public void renameSecureContainer(String oldId, String newId) throws IllegalStateException {
+        String cmd = String.format("rename_asec %s %s", oldId, newId);
+        mConnector.doCommand(cmd);
+    }
+
     public String getSecureContainerPath(String id) throws IllegalStateException {
         ArrayList<String> rsp = mConnector.doCommand("asec_path " + id);
 
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index 8594e44..b34b50a 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -78,7 +78,7 @@
      *
      * @param context  Binder context for this service
      */
-    private NetworkManagementService(Context context) {
+    public NetworkManagementService(Context context) {
         mContext = context;
 
         mObservers = new ArrayList<INetworkManagementEventObserver>();
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index cafc804..1307972 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -2855,9 +2855,8 @@
     // room left on the data partition, or a ZipException if the package
     // file is malformed.
     //
-    private int cachePackageSharedLibsForAbiLI( PackageParser.Package  pkg,
-        File dataPath, File scanFile, String cpuAbi)
-    throws IOException, ZipException {
+    private int cachePackageSharedLibsForAbiLI(PackageParser.Package pkg,
+        File dataPath, File scanFile, String cpuAbi) throws IOException, ZipException {
         File sharedLibraryDir = new File(dataPath.getPath() + "/lib");
         final String apkLib = "lib/";
         final int apkLibLen = apkLib.length();
@@ -2935,7 +2934,7 @@
                 if (mInstaller == null) {
                     sharedLibraryDir.mkdir();
                 }
-                cacheSharedLibLI(pkg, zipFile, entry, sharedLibraryDir,
+                cacheNativeBinaryLI(pkg, zipFile, entry, sharedLibraryDir,
                         sharedLibraryFile);
             }
         }
@@ -2948,6 +2947,54 @@
         return PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES;
     }
 
+    // Find the gdbserver executable program in a package at
+    // lib/<cpuAbi>/gdbserver and copy it to /data/data/<name>/lib/gdbserver
+    //
+    // Returns PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES on success,
+    // or PACKAGE_INSTALL_NATIVE_NO_LIBRARIES otherwise.
+    //
+    private int cachePackageGdbServerLI(PackageParser.Package pkg,
+        File dataPath, File scanFile, String cpuAbi) throws IOException, ZipException {
+        File installGdbServerDir = new File(dataPath.getPath() + "/lib");
+        final String GDBSERVER = "gdbserver";
+        final String apkGdbServerPath = "lib/" + cpuAbi + "/" + GDBSERVER;
+
+        ZipFile zipFile = new ZipFile(scanFile);
+        Enumeration<ZipEntry> entries =
+            (Enumeration<ZipEntry>) zipFile.entries();
+
+        while (entries.hasMoreElements()) {
+            ZipEntry entry = entries.nextElement();
+            // skip directories
+            if (entry.isDirectory()) {
+                continue;
+            }
+            String entryName = entry.getName();
+
+            if (!entryName.equals(apkGdbServerPath)) {
+                continue;
+            }
+
+            String installGdbServerPath = installGdbServerDir.getPath() +
+                "/" + GDBSERVER;
+            File installGdbServerFile = new File(installGdbServerPath);
+            if (! installGdbServerFile.exists() ||
+                installGdbServerFile.length() != entry.getSize() ||
+                installGdbServerFile.lastModified() != entry.getTime()) {
+                if (Config.LOGD) {
+                    Log.d(TAG, "Caching gdbserver " + entry.getName());
+                }
+                if (mInstaller == null) {
+                    installGdbServerDir.mkdir();
+                }
+                cacheNativeBinaryLI(pkg, zipFile, entry, installGdbServerDir,
+                        installGdbServerFile);
+            }
+            return PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES;
+        }
+        return PACKAGE_INSTALL_NATIVE_NO_LIBRARIES;
+    }
+
     // extract shared libraries stored in the APK as lib/<cpuAbi>/lib<name>.so
     // and copy them to /data/data/<appname>/lib.
     //
@@ -2957,7 +3004,7 @@
     //
     private int cachePackageSharedLibsLI(PackageParser.Package  pkg,
         File dataPath, File scanFile) {
-        final String cpuAbi = Build.CPU_ABI;
+        String cpuAbi = Build.CPU_ABI;
         try {
             int result = cachePackageSharedLibsForAbiLI(pkg, dataPath, scanFile, cpuAbi);
 
@@ -2968,7 +3015,7 @@
             //
             // only scan the package twice in case of ABI mismatch
             if (result == PACKAGE_INSTALL_NATIVE_ABI_MISMATCH) {
-                String  cpuAbi2 = SystemProperties.get("ro.product.cpu.abi2",null);
+                final String cpuAbi2 = SystemProperties.get("ro.product.cpu.abi2",null);
                 if (cpuAbi2 != null) {
                     result = cachePackageSharedLibsForAbiLI(pkg, dataPath, scanFile, cpuAbi2);
                 }
@@ -2977,6 +3024,20 @@
                     Log.w(TAG,"Native ABI mismatch from package file");
                     return PackageManager.INSTALL_FAILED_INVALID_APK;
                 }
+
+                if (result == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES) {
+                    cpuAbi = cpuAbi2;
+                }
+            }
+
+            // for debuggable packages, also extract gdbserver from lib/<abi>
+            // into /data/data/<appname>/lib too.
+            if (result == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES &&
+                (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
+                int result2 = cachePackageGdbServerLI(pkg, dataPath, scanFile, cpuAbi);
+                if (result2 == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES) {
+                    pkg.applicationInfo.flags |= ApplicationInfo.FLAG_NATIVE_DEBUGGABLE;
+                }
             }
         } catch (ZipException e) {
             Log.w(TAG, "Failed to extract data from package file", e);
@@ -2988,26 +3049,27 @@
         return PackageManager.INSTALL_SUCCEEDED;
     }
 
-    private void cacheSharedLibLI(PackageParser.Package pkg,
+    private void cacheNativeBinaryLI(PackageParser.Package pkg,
             ZipFile zipFile, ZipEntry entry,
-            File sharedLibraryDir,
-            File sharedLibraryFile) throws IOException {
+            File binaryDir,
+            File binaryFile) throws IOException {
         InputStream inputStream = zipFile.getInputStream(entry);
         try {
-            File tempFile = File.createTempFile("tmp", "tmp", sharedLibraryDir);
+            File tempFile = File.createTempFile("tmp", "tmp", binaryDir);
             String tempFilePath = tempFile.getPath();
-            // XXX package manager can't change owner, so the lib files for
+            // XXX package manager can't change owner, so the executable files for
             // now need to be left as world readable and owned by the system.
             if (! FileUtils.copyToFile(inputStream, tempFile) ||
                 ! tempFile.setLastModified(entry.getTime()) ||
                 FileUtils.setPermissions(tempFilePath,
                         FileUtils.S_IRUSR|FileUtils.S_IWUSR|FileUtils.S_IRGRP
+                        |FileUtils.S_IXUSR|FileUtils.S_IXGRP|FileUtils.S_IXOTH
                         |FileUtils.S_IROTH, -1, -1) != 0 ||
-                ! tempFile.renameTo(sharedLibraryFile)) {
+                ! tempFile.renameTo(binaryFile)) {
                 // Failed to properly write file.
                 tempFile.delete();
-                throw new IOException("Couldn't create cached shared lib "
-                        + sharedLibraryFile + " in " + sharedLibraryDir);
+                throw new IOException("Couldn't create cached binary "
+                        + binaryFile + " in " + binaryDir);
             }
         } finally {
             inputStream.close();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 6b3f433..22447ed 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -249,6 +249,14 @@
             }
 
             try {
+                Log.i(TAG, "NetworkManagement Service");
+                ServiceManager.addService(
+                        Context.NETWORKMANAGEMENT_SERVICE, new NetworkManagementService(context));
+            } catch (Throwable e) {
+                Log.e(TAG, "Failure starting NetworkManagement Service", e);
+            }
+
+            try {
                 Log.i(TAG, "Connectivity Service");
                 connectivity = ConnectivityService.getInstance(context);
                 ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity);
diff --git a/telephony/java/com/android/internal/telephony/MccTable.java b/telephony/java/com/android/internal/telephony/MccTable.java
index e25eb7b..7f62169 100644
--- a/telephony/java/com/android/internal/telephony/MccTable.java
+++ b/telephony/java/com/android/internal/telephony/MccTable.java
@@ -590,7 +590,7 @@
             if (mcc != 0) {
                 setTimezoneFromMccIfNeeded(phone, mcc);
                 setLocaleFromMccIfNeeded(phone, mcc);
-                setWifiChannelsFromMccIfNeeded(phone, mcc);
+                setWifiChannelsFromMcc(phone, mcc);
             }
             try {
                 Configuration config = ActivityManagerNative.getDefault().getConfiguration();
@@ -646,20 +646,14 @@
      * @param phone PhoneBase to act on (get context from).
      * @param mcc Mobile Country Code of the SIM or SIM-like entity (build prop on CDMA)
      */
-    private static void setWifiChannelsFromMccIfNeeded(PhoneBase phone, int mcc) {
+    private static void setWifiChannelsFromMcc(PhoneBase phone, int mcc) {
         int wifiChannels = MccTable.wifiChannelsForMcc(mcc);
         if (wifiChannels != 0) {
             Context context = phone.getContext();
-            // only set to this default if the user hasn't manually set it
-            try {
-                Settings.Secure.getInt(context.getContentResolver(),
-                        Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS);
-            } catch (Settings.SettingNotFoundException e) {
-                Log.d(LOG_TAG, "WIFI_NUM_ALLOWED_CHANNESL set to " + wifiChannels);
-                WifiManager wM = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
-                // don't persist
-                wM.setNumAllowedChannels(wifiChannels, false);
-            }
+            Log.d(LOG_TAG, "WIFI_NUM_ALLOWED_CHANNELS set to " + wifiChannels);
+            WifiManager wM = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+            //persist
+            wM.setNumAllowedChannels(wifiChannels, true);
         }
     }
 }
diff --git a/tests/CoreTests/android/content/SyncQueueTest.java b/tests/CoreTests/android/content/SyncQueueTest.java
new file mode 100644
index 0000000..5f4ab78
--- /dev/null
+++ b/tests/CoreTests/android/content/SyncQueueTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.test.AndroidTestCase;
+import android.test.RenamingDelegatingContext;
+import android.test.mock.MockContext;
+import android.test.mock.MockContentResolver;
+import android.accounts.Account;
+import android.os.Bundle;
+import android.os.SystemClock;
+
+public class SyncQueueTest extends AndroidTestCase {
+    private static final Account ACCOUNT1 = new Account("test.account1", "test.type1");
+    private static final Account ACCOUNT2 = new Account("test.account2", "test.type2");
+    private static final String AUTHORITY1 = "test.authority1";
+    private static final String AUTHORITY2 = "test.authority2";
+    private static final String AUTHORITY3 = "test.authority3";
+
+    private SyncStorageEngine mSettings;
+    private SyncQueue mSyncQueue;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        MockContentResolver mockResolver = new MockContentResolver();
+        mSettings = SyncStorageEngine.newTestInstance(new TestContext(mockResolver, getContext()));
+        mSyncQueue = new SyncQueue(mSettings);
+    }
+
+    public void testSyncQueueOrder() throws Exception {
+        final SyncOperation op1 = new SyncOperation(
+                ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("1"), 0);
+        final SyncOperation op2 = new SyncOperation(
+                ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("2"), 100);
+        final SyncOperation op3 = new SyncOperation(
+                ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("3"), 150);
+        final SyncOperation op4 = new SyncOperation(
+                ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("4"), 60);
+        final SyncOperation op5 = new SyncOperation(
+                ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("5"), 80);
+        final SyncOperation op6 = new SyncOperation(
+                ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("6"), 0);
+        op6.expedited = true;
+
+        mSyncQueue.add(op1);
+        mSyncQueue.add(op2);
+        mSyncQueue.add(op3);
+        mSyncQueue.add(op4);
+        mSyncQueue.add(op5);
+        mSyncQueue.add(op6);
+
+        long now = SystemClock.elapsedRealtime() + 200;
+
+        assertEquals(op6, mSyncQueue.nextReadyToRun(now));
+        mSyncQueue.remove(op6);
+
+        assertEquals(op1, mSyncQueue.nextReadyToRun(now));
+        mSyncQueue.remove(op1);
+
+        assertEquals(op4, mSyncQueue.nextReadyToRun(now));
+        mSyncQueue.remove(op4);
+
+        assertEquals(op5, mSyncQueue.nextReadyToRun(now));
+        mSyncQueue.remove(op5);
+
+        assertEquals(op2, mSyncQueue.nextReadyToRun(now));
+        mSyncQueue.remove(op2);
+
+        assertEquals(op3, mSyncQueue.nextReadyToRun(now));
+        mSyncQueue.remove(op3);
+    }
+
+    public void testOrderWithBackoff() throws Exception {
+        final SyncOperation op1 = new SyncOperation(
+                ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("1"), 0);
+        final SyncOperation op2 = new SyncOperation(
+                ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("2"), 100);
+        final SyncOperation op3 = new SyncOperation(
+                ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("3"), 150);
+        final SyncOperation op4 = new SyncOperation(
+                ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY3, newTestBundle("4"), 60);
+        final SyncOperation op5 = new SyncOperation(
+                ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("5"), 80);
+        final SyncOperation op6 = new SyncOperation(
+                ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("6"), 0);
+        op6.expedited = true;
+
+        mSyncQueue.add(op1);
+        mSyncQueue.add(op2);
+        mSyncQueue.add(op3);
+        mSyncQueue.add(op4);
+        mSyncQueue.add(op5);
+        mSyncQueue.add(op6);
+
+        long now = SystemClock.elapsedRealtime() + 200;
+
+        assertEquals(op6, mSyncQueue.nextReadyToRun(now));
+        mSyncQueue.remove(op6);
+
+        assertEquals(op1, mSyncQueue.nextReadyToRun(now));
+        mSyncQueue.remove(op1);
+
+        mSettings.setBackoff(ACCOUNT2,  AUTHORITY3, now + 200, 5);
+        assertEquals(op5, mSyncQueue.nextReadyToRun(now));
+
+        mSettings.setBackoff(ACCOUNT2,  AUTHORITY3, SyncStorageEngine.NOT_IN_BACKOFF_MODE, 0);
+        assertEquals(op4, mSyncQueue.nextReadyToRun(now));
+
+        mSettings.setDelayUntilTime(ACCOUNT2,  AUTHORITY3, now + 200);
+        assertEquals(op5, mSyncQueue.nextReadyToRun(now));
+
+        mSettings.setDelayUntilTime(ACCOUNT2,  AUTHORITY3, 0);
+        assertEquals(op4, mSyncQueue.nextReadyToRun(now));
+        mSyncQueue.remove(op4);
+
+        assertEquals(op5, mSyncQueue.nextReadyToRun(now));
+        mSyncQueue.remove(op5);
+
+        assertEquals(op2, mSyncQueue.nextReadyToRun(now));
+        mSyncQueue.remove(op2);
+
+        assertEquals(op3, mSyncQueue.nextReadyToRun(now));
+        mSyncQueue.remove(op3);
+    }
+
+    Bundle newTestBundle(String val) {
+        Bundle bundle = new Bundle();
+        bundle.putString("test", val);
+        return bundle;
+    }
+
+    static class TestContext extends ContextWrapper {
+        ContentResolver mResolver;
+
+        public TestContext(ContentResolver resolver, Context realContext) {
+            super(new RenamingDelegatingContext(new MockContext(), realContext, "test."));
+            mResolver = resolver;
+        }
+
+        @Override
+        public void enforceCallingOrSelfPermission(String permission, String message) {
+        }
+
+        @Override
+        public ContentResolver getContentResolver() {
+            return mResolver;
+        }
+    }
+}