Merge "changing vec4 to vec2 for perf reasons."
diff --git a/api/current.xml b/api/current.xml
index 07724ab..953c9f3 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -2103,7 +2103,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843563"
+ value="16843567"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -2125,7 +2125,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843572"
+ value="16843576"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -2136,7 +2136,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843571"
+ value="16843575"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -2147,7 +2147,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843573"
+ value="16843577"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -2191,7 +2191,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843580"
+ value="16843584"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -2213,7 +2213,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843576"
+ value="16843580"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -2235,7 +2235,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843574"
+ value="16843578"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -2246,7 +2246,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843581"
+ value="16843585"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -2257,7 +2257,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843582"
+ value="16843586"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -2466,7 +2466,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843570"
+ value="16843574"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -2807,7 +2807,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843589"
+ value="16843593"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -2818,7 +2818,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843588"
+ value="16843592"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -4497,6 +4497,28 @@
  visibility="public"
 >
 </field>
+<field name="fragmentNextEnterAnimation"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843563"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="fragmentNextExitAnimation"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843564"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="fragmentOpenEnterAnimation"
  type="int"
  transient="false"
@@ -4519,6 +4541,28 @@
  visibility="public"
 >
 </field>
+<field name="fragmentPrevEnterAnimation"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843565"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="fragmentPrevExitAnimation"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843566"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="freezesText"
  type="int"
  transient="false"
@@ -4996,7 +5040,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843579"
+ value="16843583"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -5117,7 +5161,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843566"
+ value="16843570"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -5128,7 +5172,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843564"
+ value="16843568"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -5139,7 +5183,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843565"
+ value="16843569"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -5502,7 +5546,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843575"
+ value="16843579"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -6778,7 +6822,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843568"
+ value="16843572"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -6822,7 +6866,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843590"
+ value="16843594"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -6833,7 +6877,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843584"
+ value="16843588"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -6921,7 +6965,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843592"
+ value="16843596"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -7735,7 +7779,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843585"
+ value="16843589"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -9044,7 +9088,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843569"
+ value="16843573"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -9066,7 +9110,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843567"
+ value="16843571"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -9165,7 +9209,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843583"
+ value="16843587"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -9396,7 +9440,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843578"
+ value="16843582"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -9781,7 +9825,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843586"
+ value="16843590"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -9858,7 +9902,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843587"
+ value="16843591"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -9902,7 +9946,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843591"
+ value="16843595"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -10320,7 +10364,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="16843577"
+ value="16843581"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -30872,6 +30916,17 @@
  visibility="public"
 >
 </field>
+<field name="TRANSIT_FRAGMENT_NEXT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4099"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="TRANSIT_FRAGMENT_OPEN"
  type="int"
  transient="false"
@@ -30883,6 +30938,17 @@
  visibility="public"
 >
 </field>
+<field name="TRANSIT_FRAGMENT_PREV"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="8196"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="TRANSIT_NONE"
  type="int"
  transient="false"
@@ -143262,6 +143328,8 @@
 </parameter>
 <parameter name="args" type="android.os.Bundle">
 </parameter>
+<parameter name="next" type="boolean">
+</parameter>
 </method>
 <field name="EXTRA_NO_HEADERS"
  type="java.lang.String"
@@ -203475,7 +203543,7 @@
 <class name="View.DragThumbnailBuilder"
  extends="java.lang.Object"
  abstract="false"
- static="false"
+ static="true"
  final="false"
  deprecated="not deprecated"
  visibility="public"
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index b558318..b34c243 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1341,6 +1341,18 @@
             return true;
         }
 
+        case CHECK_GRANT_URI_PERMISSION_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            int callingUid = data.readInt();
+            String targetPkg = data.readString();
+            Uri uri = Uri.CREATOR.createFromParcel(data);
+            int modeFlags = data.readInt();
+            int res = checkGrantUriPermission(callingUid, targetPkg, uri, modeFlags);
+            reply.writeNoException();
+            reply.writeInt(res);
+            return true;
+        }
+
         case DUMP_HEAP_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             String process = data.readString();
@@ -2998,6 +3010,23 @@
         reply.recycle();
     }
 
+    public int checkGrantUriPermission(int callingUid, String targetPkg,
+            Uri uri, int modeFlags) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(callingUid);
+        data.writeString(targetPkg);
+        uri.writeToParcel(data, 0);
+        data.writeInt(modeFlags);
+        mRemote.transact(CHECK_GRANT_URI_PERMISSION_TRANSACTION, data, reply, 0);
+        reply.readException();
+        int res = reply.readInt();
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+
     public boolean dumpHeap(String process, boolean managed,
             String path, ParcelFileDescriptor fd) throws RemoteException {
         Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index df18ce7..f3f7ee7 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -77,7 +77,6 @@
 import com.android.internal.os.RuntimeInit;
 import com.android.internal.os.SamplingProfilerIntegration;
 
-import dalvik.system.VMDebug;
 import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl;
 
 import java.io.File;
@@ -710,14 +709,14 @@
             long dalvikAllocated = dalvikMax - dalvikFree;
             long viewInstanceCount = ViewDebug.getViewInstanceCount();
             long viewRootInstanceCount = ViewDebug.getViewRootInstanceCount();
-            long appContextInstanceCount = VMDebug.countInstancesOfClass(ContextImpl.class);
-            long activityInstanceCount = VMDebug.countInstancesOfClass(Activity.class);
+            long appContextInstanceCount = Debug.countInstancesOfClass(ContextImpl.class);
+            long activityInstanceCount = Debug.countInstancesOfClass(Activity.class);
             int globalAssetCount = AssetManager.getGlobalAssetCount();
             int globalAssetManagerCount = AssetManager.getGlobalAssetManagerCount();
             int binderLocalObjectCount = Debug.getBinderLocalObjectCount();
             int binderProxyObjectCount = Debug.getBinderProxyObjectCount();
             int binderDeathObjectCount = Debug.getBinderDeathObjectCount();
-            int openSslSocketCount = OpenSSLSocketImpl.getInstanceCount();
+            long openSslSocketCount = Debug.countInstancesOfClass(OpenSSLSocketImpl.class);
             long sqliteAllocated = SQLiteDebug.getHeapAllocatedSize() / 1024;
             SQLiteDebug.PagerStats stats = SQLiteDebug.getDatabaseInfo();
 
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index da7ba6f..37e7253 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -1380,6 +1380,12 @@
             case FragmentTransaction.TRANSIT_FRAGMENT_CLOSE:
                 rev = FragmentTransaction.TRANSIT_FRAGMENT_OPEN;
                 break;
+            case FragmentTransaction.TRANSIT_FRAGMENT_NEXT:
+                rev = FragmentTransaction.TRANSIT_FRAGMENT_PREV;
+                break;
+            case FragmentTransaction.TRANSIT_FRAGMENT_PREV:
+                rev = FragmentTransaction.TRANSIT_FRAGMENT_NEXT;
+                break;
         }
         return rev;
         
@@ -1398,6 +1404,16 @@
                     ? com.android.internal.R.styleable.FragmentAnimation_fragmentCloseEnterAnimation
                     : com.android.internal.R.styleable.FragmentAnimation_fragmentCloseExitAnimation;
                 break;
+            case FragmentTransaction.TRANSIT_FRAGMENT_NEXT:
+                animAttr = enter
+                    ? com.android.internal.R.styleable.FragmentAnimation_fragmentNextEnterAnimation
+                    : com.android.internal.R.styleable.FragmentAnimation_fragmentNextExitAnimation;
+                break;
+            case FragmentTransaction.TRANSIT_FRAGMENT_PREV:
+                animAttr = enter
+                    ? com.android.internal.R.styleable.FragmentAnimation_fragmentPrevEnterAnimation
+                    : com.android.internal.R.styleable.FragmentAnimation_fragmentPrevExitAnimation;
+                break;
         }
         return animAttr;
     }
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
index 09d8d26..b00476bb 100644
--- a/core/java/android/app/FragmentTransaction.java
+++ b/core/java/android/app/FragmentTransaction.java
@@ -106,10 +106,14 @@
     public final int TRANSIT_UNSET = -1;
     /** No animation for transition. */
     public final int TRANSIT_NONE = 0;
-    /** Fragment is being added */
+    /** Fragment is being added onto the stack */
     public final int TRANSIT_FRAGMENT_OPEN = 1 | TRANSIT_ENTER_MASK;
-    /** Fragment is being removed */
+    /** Fragment is being removed from the stack */
     public final int TRANSIT_FRAGMENT_CLOSE = 2 | TRANSIT_EXIT_MASK;
+    /** Fragment is being added in a 'next' operation*/
+    public final int TRANSIT_FRAGMENT_NEXT = 3 | TRANSIT_ENTER_MASK;
+    /** Fragment is being removed in a 'previous' operation */
+    public final int TRANSIT_FRAGMENT_PREV = 4 | TRANSIT_EXIT_MASK;
 
     /**
      * Set specific animation resources to run for the fragments that are
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 4d73817..cd229e3 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -326,6 +326,9 @@
     public void revokeUriPermissionFromOwner(IBinder owner, Uri uri,
             int mode) throws RemoteException;
 
+    public int checkGrantUriPermission(int callingUid, String targetPkg,
+            Uri uri, int modeFlags) throws RemoteException;
+
     // Cause the specified process to dump the specified heap.
     public boolean dumpHeap(String process, boolean managed, String path,
         ParcelFileDescriptor fd) throws RemoteException;
@@ -540,5 +543,6 @@
     int NEW_URI_PERMISSION_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+115;
     int GRANT_URI_PERMISSION_FROM_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+116;
     int REVOKE_URI_PERMISSION_FROM_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+117;
-    int DUMP_HEAP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+118;
+    int CHECK_GRANT_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+118;
+    int DUMP_HEAP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+119;
 }
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index 0ea0648..85a6765 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -108,7 +108,7 @@
      */
     public ClipData getPrimaryClip() {
         try {
-            return getService().getPrimaryClip();
+            return getService().getPrimaryClip(mContext.getPackageName());
         } catch (RemoteException e) {
             return null;
         }
diff --git a/core/java/android/content/IClipboard.aidl b/core/java/android/content/IClipboard.aidl
index 3e1fe55..254901b 100644
--- a/core/java/android/content/IClipboard.aidl
+++ b/core/java/android/content/IClipboard.aidl
@@ -27,7 +27,7 @@
  */
 interface IClipboard {
     void setPrimaryClip(in ClipData clip);
-    ClipData getPrimaryClip();
+    ClipData getPrimaryClip(String pkg);
     ClipDescription getPrimaryClipDescription();
     boolean hasPrimaryClip();
     void addPrimaryClipChangedListener(in IOnPrimaryClipChangedListener listener);
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 950d339..c9115c5 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -16,6 +16,18 @@
 
 package android.content;
 
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
 
 import com.android.internal.R;
@@ -36,17 +48,6 @@
 import android.content.pm.RegisteredServicesCacheListener;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.text.format.DateUtils;
@@ -58,11 +59,13 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Random;
-import java.util.Collection;
 import java.util.concurrent.CountDownLatch;
 
 /**
@@ -81,30 +84,17 @@
     private static final long MAX_TIME_PER_SYNC;
 
     static {
-        String localSyncDelayString = SystemProperties.get("sync.local_sync_delay");
-        long localSyncDelay = 30 * 1000; // 30 seconds
-        if (localSyncDelayString != null) {
-            try {
-                localSyncDelay = Long.parseLong(localSyncDelayString);
-            } catch (NumberFormatException nfe) {
-                // ignore, use default
-            }
-        }
-        LOCAL_SYNC_DELAY = localSyncDelay;
-
-        String maxTimePerSyncString = SystemProperties.get("sync.max_time_per_sync");
-        long maxTimePerSync = 5 * 60 * 1000; // 5 minutes
-        if (maxTimePerSyncString != null) {
-            try {
-                maxTimePerSync = Long.parseLong(maxTimePerSyncString);
-            } catch (NumberFormatException nfe) {
-                // ignore, use default
-            }
-        }
-        MAX_TIME_PER_SYNC = maxTimePerSync;
+        MAX_SIMULTANEOUS_INITIALIZATION_SYNCS = SystemProperties.getInt("sync.max_init_syncs", 5);
+        MAX_SIMULTANEOUS_REGULAR_SYNCS = SystemProperties.getInt("sync.max_regular_syncs", 2);
+        LOCAL_SYNC_DELAY =
+                SystemProperties.getLong("sync.local_sync_delay", 30 * 1000 /* 30 seconds */);
+        MAX_TIME_PER_SYNC =
+                SystemProperties.getLong("sync.max_time_per_sync", 5 * 60 * 1000 /* 5 minutes */);
+        SYNC_NOTIFICATION_DELAY =
+                SystemProperties.getLong("sync.notification_delay", 30 * 1000 /* 30 seconds */);
     }
 
-    private static final long SYNC_NOTIFICATION_DELAY = 30 * 1000; // 30 seconds
+    private static final long SYNC_NOTIFICATION_DELAY;
 
     /**
      * When retrying a sync for the first time use this delay. After that
@@ -123,21 +113,21 @@
      */
     private static final int DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS = 10;
 
-    /**
-     * An error notification is sent if sync of any of the providers has been failing for this long.
-     */
-    private static final long ERROR_NOTIFICATION_DELAY_MS = 1000 * 60 * 10; // 10 minutes
-
     private static final int INITIALIZATION_UNBIND_DELAY_MS = 5000;
 
     private static final String SYNC_WAKE_LOCK_PREFIX = "*sync*";
     private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm";
+    private static final String SYNC_LOOP_WAKE_LOCK = "SyncLoopWakeLock";
+
+    private static final int MAX_SIMULTANEOUS_REGULAR_SYNCS;
+    private static final int MAX_SIMULTANEOUS_INITIALIZATION_SYNCS;
 
     private Context mContext;
 
     private volatile Account[] mAccounts = INITIAL_ACCOUNTS_ARRAY;
 
     volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
+    volatile private PowerManager.WakeLock mSyncManagerWakeLock;
     volatile private boolean mDataConnectionIsConnected = false;
     volatile private boolean mStorageIsLow = false;
 
@@ -147,10 +137,8 @@
     private final SyncStorageEngine mSyncStorageEngine;
     public final SyncQueue mSyncQueue;
 
-    private ActiveSyncContext mActiveSyncContext = null;
+    private final ArrayList<ActiveSyncContext> mActiveSyncContexts = Lists.newArrayList();
 
-    // set if the sync error indicator should be reported.
-    private boolean mNeedSyncErrorNotification = false;
     // set if the sync active indicator should be reported
     private boolean mNeedSyncActiveNotification = false;
 
@@ -200,6 +188,9 @@
 
     private final PowerManager mPowerManager;
 
+    private static final long SYNC_ALARM_TIMEOUT_MIN = 30 * 1000; // 30 seconds
+    private static final long SYNC_ALARM_TIMEOUT_MAX = 2 * 60 * 60 * 1000; // two hours
+
     public void onAccountsUpdated(Account[] accounts) {
         // remember if this was the first time this was called after an update
         final boolean justBootedUp = mAccounts == INITIAL_ACCOUNTS_ARRAY;
@@ -207,11 +198,10 @@
 
         // if a sync is in progress yet it is no longer in the accounts list,
         // cancel it
-        ActiveSyncContext activeSyncContext = mActiveSyncContext;
-        if (activeSyncContext != null) {
-            if (!ArrayUtils.contains(accounts, activeSyncContext.mSyncOperation.account)) {
+        for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
+            if (!ArrayUtils.contains(accounts, currentSyncContext.mSyncOperation.account)) {
                 Log.d(TAG, "canceling sync since the account has been removed");
-                sendSyncFinishedOrCanceledMessage(activeSyncContext,
+                sendSyncFinishedOrCanceledMessage(currentSyncContext,
                         null /* no result since this is a cancel */);
             }
         }
@@ -238,6 +228,7 @@
             // If this was the bootup case then don't sync everything, instead only
             // sync those that have an unknown syncable state, which will give them
             // a chance to set their syncable state.
+
             boolean onlyThoseWithUnkownSyncableState = justBootedUp;
             scheduleSync(null, null, null, 0 /* no delay */, onlyThoseWithUnkownSyncableState);
         }
@@ -371,6 +362,15 @@
                 HANDLE_SYNC_ALARM_WAKE_LOCK);
         mHandleAlarmWakeLock.setReferenceCounted(false);
 
+        // This WakeLock is used to ensure that we stay awake while running the sync loop
+        // message handler. Normally we will hold a sync adapter wake lock while it is being
+        // synced but during the execution of the sync loop it might finish a sync for
+        // one sync adapter before starting the sync for the other sync adapter and we
+        // don't want the device to go to sleep during that window.
+        mSyncManagerWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                SYNC_LOOP_WAKE_LOCK);
+        mSyncManagerWakeLock.setReferenceCounted(false);
+
         mSyncStorageEngine.addStatusChangeListener(
                 ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, new ISyncStatusObserver.Stub() {
             public void onStatusChanged(int which) {
@@ -427,8 +427,8 @@
         Intent intent = new Intent();
         intent.setAction("android.content.SyncAdapter");
         intent.setComponent(syncAdapterInfo.componentName);
-        if (!mContext.bindService(intent, new InitializerServiceConnection(account, authority, mContext,
-                mMainHandler),
+        if (!mContext.bindService(intent,
+                new InitializerServiceConnection(account, authority, mContext, mMainHandler),
                 Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND)) {
             Log.w(TAG, "initializeSyncAdapter: failed to bind to " + intent);
         }
@@ -508,8 +508,8 @@
      * @param requestedAccount the account to sync, may be null to signify all accounts
      * @param requestedAuthority the authority to sync, may be null to indicate all authorities
      * @param extras a Map of SyncAdapter-specific information to control
-*          syncs of a specific provider. Can be null. Is ignored
-*          if the url is null.
+     *          syncs of a specific provider. Can be null. Is ignored
+     *          if the url is null.
      * @param delay how many milliseconds in the future to wait before performing this
      * @param onlyThoseWithUnkownSyncableState
      */
@@ -613,16 +613,29 @@
                         continue;
                     }
 
-                    if (isLoggable) {
-                        Log.v(TAG, "scheduleSync:"
-                                + " delay " + delay
-                                + ", source " + source
-                                + ", account " + account
-                                + ", authority " + authority
-                                + ", extras " + extras);
+                    Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(account, authority);
+                    long delayUntil = mSyncStorageEngine.getDelayUntilTime(account, authority);
+                    final long backoffTime = backoff != null ? backoff.first : 0;
+                    if (isSyncable < 0) {
+                        Bundle newExtras = new Bundle();
+                        newExtras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
+                        scheduleSyncOperation(
+                                new SyncOperation(account, source, authority, newExtras, 0,
+                                        backoffTime, delayUntil));
                     }
-                    scheduleSyncOperation(
-                            new SyncOperation(account, source, authority, extras, delay));
+                    if (!onlyThoseWithUnkownSyncableState) {
+                        if (isLoggable) {
+                            Log.v(TAG, "scheduleSync:"
+                                    + " delay " + delay
+                                    + ", source " + source
+                                    + ", account " + account
+                                    + ", authority " + authority
+                                    + ", extras " + extras);
+                        }
+                        scheduleSyncOperation(
+                                new SyncOperation(account, source, authority, extras, delay,
+                                        backoffTime, delayUntil));
+                    }
                 }
             }
         }
@@ -636,7 +649,8 @@
     }
 
     public SyncAdapterType[] getSyncAdapterTypes() {
-        final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> serviceInfos =
+        final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>>
+                serviceInfos =
                 mSyncAdapters.getAllServices();
         SyncAdapterType[] types = new SyncAdapterType[serviceInfos.size()];
         int i = 0;
@@ -666,6 +680,14 @@
         mSyncHandler.sendMessage(msg);
     }
 
+    private void sendCancelSyncsMessage(final Account account, final String authority) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CANCEL");
+        Message msg = mSyncHandler.obtainMessage();
+        msg.what = SyncHandler.MESSAGE_CANCEL;
+        msg.obj = Pair.create(account, authority);
+        mSyncHandler.sendMessage(msg);
+    }
+
     class SyncHandlerMessagePayload {
         public final ActiveSyncContext activeSyncContext;
         public final SyncResult syncResult;
@@ -683,11 +705,6 @@
         }
     }
 
-    private void clearBackoffSetting(SyncOperation op) {
-        mSyncStorageEngine.setBackoff(op.account, op.authority,
-                SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
-    }
-
     private void increaseBackoffSetting(SyncOperation op) {
         final long now = SystemClock.elapsedRealtime();
 
@@ -713,6 +730,9 @@
 
         mSyncStorageEngine.setBackoff(op.account, op.authority,
                 now + newDelayInMs, newDelayInMs);
+        synchronized (mSyncQueue) {
+            mSyncQueue.onBackoffChanged(op.account, op.authority, now + newDelayInMs);
+        }
     }
 
     private void setDelayUntilTime(SyncOperation op, long delayUntilSeconds) {
@@ -725,6 +745,9 @@
             newDelayUntilTime = 0;
         }
         mSyncStorageEngine.setDelayUntilTime(op.account, op.authority, newDelayUntilTime);
+        synchronized (mSyncQueue) {
+            mSyncQueue.onDelayUntilTimeChanged(op.account, op.authority, newDelayUntilTime);
+        }
     }
 
     /**
@@ -733,23 +756,7 @@
      * @param authority limit the cancelations to syncs with this authority, if non-null
      */
     public void cancelActiveSync(Account account, String authority) {
-        ActiveSyncContext activeSyncContext = mActiveSyncContext;
-        if (activeSyncContext != null) {
-            // if an authority was specified then only cancel the sync if it matches
-            if (account != null) {
-                if (!account.equals(activeSyncContext.mSyncOperation.account)) {
-                    return;
-                }
-            }
-            // if an account was specified then only cancel the sync if it matches
-            if (authority != null) {
-                if (!authority.equals(activeSyncContext.mSyncOperation.authority)) {
-                    return;
-                }
-            }
-            sendSyncFinishedOrCanceledMessage(activeSyncContext,
-                    null /* no result since this is a cancel */);
-        }
+        sendCancelSyncsMessage(account, authority);
     }
 
     /**
@@ -758,22 +765,6 @@
      * @param syncOperation the SyncOperation to schedule
      */
     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 ActiveSyncContext activeSyncContext = mActiveSyncContext;
-        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 (!activeSyncContext.mSyncOperation.expedited && !hasSameKey) {
-                scheduleSyncOperation(new SyncOperation(activeSyncContext.mSyncOperation));
-                sendSyncFinishedOrCanceledMessage(activeSyncContext,
-                        null /* no result since this is a cancel */);
-            }
-        }
-
         boolean queueChanged;
         synchronized (mSyncQueue) {
             queueChanged = mSyncQueue.add(syncOperation);
@@ -798,11 +789,11 @@
      * @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.remove(account, authority);
         }
+        mSyncStorageEngine.setBackoff(account, authority,
+                SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
     }
 
     void maybeRescheduleSync(SyncResult syncResult, SyncOperation operation) {
@@ -829,7 +820,8 @@
         if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)) {
             Log.d(TAG, "not retrying sync operation because SYNC_EXTRAS_DO_NOT_RETRY was specified "
                     + operation);
-        } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)) {
+        } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)
+                && !syncResult.syncAlreadyInProgress) {
             operation.extras.remove(ContentResolver.SYNC_EXTRAS_UPLOAD);
             Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
                     + "encountered an error: " + operation);
@@ -850,7 +842,8 @@
             }
             scheduleSyncOperation(new SyncOperation(operation.account, operation.syncSource,
                     operation.authority, operation.extras,
-                    DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000));
+                    DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000,
+                    operation.backoff, operation.delayUntil));
         } else if (syncResult.hasSoftError()) {
             if (isLoggable) {
                 Log.d(TAG, "retrying sync operation because it encountered a soft error: "
@@ -873,15 +866,33 @@
         final long mStartTime;
         long mTimeoutStartTime;
         boolean mBound;
+        final PowerManager.WakeLock mSyncWakeLock;
+        final int mSyncAdapterUid;
+        SyncInfo mSyncInfo;
 
-        public ActiveSyncContext(SyncOperation syncOperation,
-                long historyRowId) {
+        /**
+         * Create an ActiveSyncContext for an impending sync and grab the wakelock for that
+         * sync adapter. Since this grabs the wakelock you need to be sure to call
+         * close() when you are done with this ActiveSyncContext, whether the sync succeeded
+         * or not.
+         * @param syncOperation the SyncOperation we are about to sync
+         * @param historyRowId the row in which to record the history info for this sync
+         * @param syncAdapterUid the UID of the application that contains the sync adapter
+         * for this sync. This is used to attribute the wakelock hold to that application.
+         */
+        public ActiveSyncContext(SyncOperation syncOperation, long historyRowId,
+                int syncAdapterUid) {
             super();
+            mSyncAdapterUid = syncAdapterUid;
             mSyncOperation = syncOperation;
             mHistoryRowId = historyRowId;
             mSyncAdapter = null;
             mStartTime = SystemClock.elapsedRealtime();
             mTimeoutStartTime = mStartTime;
+            mSyncWakeLock = mSyncHandler.getSyncWakeLock(
+                    mSyncOperation.account.type, mSyncOperation.authority);
+            mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterUid));
+            mSyncWakeLock.acquire();
         }
 
         public void sendHeartbeat() {
@@ -889,6 +900,7 @@
         }
 
         public void onFinished(SyncResult result) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "onFinished: " + this);
             // include "this" in the message so that the handler can ignore it if this
             // ActiveSyncContext is no longer the mActiveSyncContext at message handling
             // time
@@ -936,6 +948,10 @@
             return bindResult;
         }
 
+        /**
+         * Performs the required cleanup, which is the releasing of the wakelock and
+         * unbinding from the sync adapter (if actually bound).
+         */
         protected void close() {
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.d(TAG, "unBindFromSyncAdapter: connection " + this);
@@ -944,6 +960,8 @@
                 mBound = false;
                 mContext.unbindService(this);
             }
+            mSyncWakeLock.setWorkSource(null);
+            mSyncWakeLock.release();
         }
 
         @Override
@@ -1003,62 +1021,28 @@
             pw.println("no alarm is scheduled (there had better not be any pending syncs)");
         }
 
-        final SyncManager.ActiveSyncContext activeSyncContext = mActiveSyncContext;
-
-        pw.print("active sync: "); pw.println(activeSyncContext);
-
         pw.print("notification info: ");
         sb.setLength(0);
         mSyncHandler.mSyncNotificationInfo.toString(sb);
         pw.println(sb.toString());
 
+        pw.println();
+        pw.println("Active Syncs: " + mActiveSyncContexts.size());
+        for (SyncManager.ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
+            final long durationInSeconds = (now - activeSyncContext.mStartTime) / 1000;
+            pw.print("  ");
+            pw.print(DateUtils.formatElapsedTime(durationInSeconds));
+            pw.print(" - ");
+            pw.print(activeSyncContext.mSyncOperation.dump(false));
+            pw.println();
+        }
+
         synchronized (mSyncQueue) {
-            pw.print("sync queue: ");
             sb.setLength(0);
             mSyncQueue.dump(sb);
-            pw.println(sb.toString());
         }
-
-        SyncInfo active = mSyncStorageEngine.getCurrentSync();
-        if (active != null) {
-            SyncStorageEngine.AuthorityInfo authority
-                    = mSyncStorageEngine.getAuthority(active.authorityId);
-            final long durationInSeconds = (now - active.startTime) / 1000;
-            pw.print("Active sync: ");
-                    pw.print(authority != null ? authority.account : "<no account>");
-                    pw.print(" ");
-                    pw.print(authority != null ? authority.authority : "<no account>");
-                    if (activeSyncContext != null) {
-                        pw.print(" ");
-                        pw.print(SyncStorageEngine.SOURCES[
-                                activeSyncContext.mSyncOperation.syncSource]);
-                    }
-                    pw.print(", duration is ");
-                    pw.println(DateUtils.formatElapsedTime(durationInSeconds));
-        } else {
-            pw.println("No sync is in progress.");
-        }
-
-        ArrayList<SyncStorageEngine.PendingOperation> ops
-                = mSyncStorageEngine.getPendingOperations();
-        if (ops != null && ops.size() > 0) {
-            pw.println();
-            pw.println("Pending Syncs");
-            final int N = ops.size();
-            for (int i=0; i<N; i++) {
-                SyncStorageEngine.PendingOperation op = ops.get(i);
-                pw.print("  #"); pw.print(i); pw.print(": account=");
-                pw.print(op.account.name); pw.print(":");
-                pw.print(op.account.type); pw.print(" 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, false /* asKey */);
-                    pw.print("    extras: "); pw.println(sb.toString());
-                }
-            }
-        }
+        pw.println();
+        pw.print(sb.toString());
 
         // join the installed sync adapter with the accounts list and emit for everything
         pw.println();
@@ -1261,7 +1245,7 @@
 
         /** Call to let the tracker know that the sync state may have changed */
         public synchronized void update() {
-            final boolean isSyncInProgress = mActiveSyncContext != null;
+            final boolean isSyncInProgress = !mActiveSyncContexts.isEmpty();
             if (isSyncInProgress == mLastWasSyncing) return;
             final long now = SystemClock.elapsedRealtime();
             if (isSyncInProgress) {
@@ -1301,17 +1285,14 @@
         private static final int MESSAGE_CHECK_ALARMS = 3;
         private static final int MESSAGE_SERVICE_CONNECTED = 4;
         private static final int MESSAGE_SERVICE_DISCONNECTED = 5;
+        private static final int MESSAGE_CANCEL = 6;
 
         public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo();
         private Long mAlarmScheduleTime = null;
         public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker();
-        private PowerManager.WakeLock mSyncWakeLock;
-        private final HashMap<Pair<String, String>,  PowerManager.WakeLock> mWakeLocks =
+        private final HashMap<Pair<String, String>, PowerManager.WakeLock> mWakeLocks =
                 Maps.newHashMap();
 
-        // used to track if we have installed the error notification so that we don't reinstall
-        // it if sync is still failing
-        private boolean mErrorNotificationInstalled = false;
         private volatile CountDownLatch mReadyToRunLatch = new CountDownLatch(1);
         public void onBootCompleted() {
             mBootCompleted = true;
@@ -1351,12 +1332,6 @@
          * Used to keep track of whether a sync notification is active and who it is for.
          */
         class SyncNotificationInfo {
-            // only valid if isActive is true
-            public Account account;
-
-            // only valid if isActive is true
-            public String authority;
-
             // true iff the notification manager has been asked to send the notification
             public boolean isActive = false;
 
@@ -1365,10 +1340,7 @@
             public Long startTime = null;
 
             public void toString(StringBuilder sb) {
-                sb.append("account ").append(account)
-                        .append(", authority ").append(authority)
-                        .append(", isActive ").append(isActive)
-                        .append(", startTime ").append(startTime);
+                sb.append("isActive ").append(isActive).append(", startTime ").append(startTime);
             }
 
             @Override
@@ -1384,60 +1356,72 @@
         }
 
         public void handleMessage(Message msg) {
-            Long earliestFuturePollTime = null;
+            long earliestFuturePollTime = Long.MAX_VALUE;
+            long nextPendingSyncTime = Long.MAX_VALUE;
             try {
                 waitUntilReadyToRun();
+                mSyncManagerWakeLock.acquire();
                 // Always do this first so that we be sure that any periodic syncs that
                 // are ready to run have been converted into pending syncs. This allows the
                 // logic that considers the next steps to take based on the set of pending syncs
                 // to also take into account the periodic syncs.
                 earliestFuturePollTime = scheduleReadyPeriodicSyncs();
                 switch (msg.what) {
+                    case SyncHandler.MESSAGE_CANCEL: {
+                        Pair<Account, String> payload = (Pair<Account, String>)msg.obj;
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CANCEL: "
+                                    + payload.first + ", " + payload.second);
+                        }
+                        cancelActiveSyncLocked(payload.first, payload.second);
+                        nextPendingSyncTime = maybeStartNextSyncLocked();
+                        break;
+                    }
+
                     case SyncHandler.MESSAGE_SYNC_FINISHED:
                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
                             Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_FINISHED");
                         }
                         SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload)msg.obj;
-                        if (mActiveSyncContext != payload.activeSyncContext) {
-                            Log.d(TAG, "handleSyncHandlerMessage: sync context doesn't match, "
-                                    + "dropping: mActiveSyncContext " + mActiveSyncContext
-                                    + " != " + payload.activeSyncContext);
-                            return;
+                        if (!isSyncStillActive(payload.activeSyncContext)) {
+                            Log.d(TAG, "handleSyncHandlerMessage: dropping since the "
+                                    + "sync is no longer active: "
+                                    + payload.activeSyncContext);
+                            break;
                         }
-                        runSyncFinishedOrCanceled(payload.syncResult);
+                        runSyncFinishedOrCanceledLocked(payload.syncResult, payload.activeSyncContext);
 
-                        // since we are no longer syncing, check if it is time to start a new sync
-                        runStateIdle();
+                        // since a sync just finished check if it is time to start a new sync
+                        nextPendingSyncTime = maybeStartNextSyncLocked();
                         break;
 
                     case SyncHandler.MESSAGE_SERVICE_CONNECTED: {
                         ServiceConnectionData msgData = (ServiceConnectionData)msg.obj;
                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
                             Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CONNECTED: "
-                                    + msgData.activeSyncContext
-                                    + " active is " + mActiveSyncContext);
+                                    + msgData.activeSyncContext);
                         }
                         // check that this isn't an old message
-                        if (mActiveSyncContext == msgData.activeSyncContext) {
-                            runBoundToSyncAdapter(msgData.syncAdapter);
+                        if (isSyncStillActive(msgData.activeSyncContext)) {
+                            runBoundToSyncAdapter(msgData.activeSyncContext, msgData.syncAdapter);
                         }
                         break;
                     }
 
                     case SyncHandler.MESSAGE_SERVICE_DISCONNECTED: {
-                        ServiceConnectionData msgData = (ServiceConnectionData)msg.obj;
+                        final ActiveSyncContext currentSyncContext =
+                                ((ServiceConnectionData)msg.obj).activeSyncContext;
                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
                             Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_DISCONNECTED: "
-                                    + msgData.activeSyncContext
-                                    + " active is " + mActiveSyncContext);
+                                    + currentSyncContext);
                         }
                         // check that this isn't an old message
-                        if (mActiveSyncContext == msgData.activeSyncContext) {
+                        if (isSyncStillActive(currentSyncContext)) {
                             // cancel the sync if we have a syncadapter, which means one is
                             // outstanding
-                            if (mActiveSyncContext.mSyncAdapter != null) {
+                            if (currentSyncContext.mSyncAdapter != null) {
                                 try {
-                                    mActiveSyncContext.mSyncAdapter.cancelSync(mActiveSyncContext);
+                                    currentSyncContext.mSyncAdapter.cancelSync(currentSyncContext);
                                 } catch (RemoteException e) {
                                     // we don't need to retry this in this case
                                 }
@@ -1447,11 +1431,10 @@
                             // which is a soft error
                             SyncResult syncResult = new SyncResult();
                             syncResult.stats.numIoExceptions++;
-                            runSyncFinishedOrCanceled(syncResult);
+                            runSyncFinishedOrCanceledLocked(syncResult, currentSyncContext);
 
-                            // since we are no longer syncing, check if it is time to start a new
-                            // sync
-                            runStateIdle();
+                            // since a sync just finished check if it is time to start a new sync
+                            nextPendingSyncTime = maybeStartNextSyncLocked();
                         }
 
                         break;
@@ -1464,22 +1447,7 @@
                         }
                         mAlarmScheduleTime = null;
                         try {
-                            if (mActiveSyncContext != null) {
-                                if (isLoggable) {
-                                    Log.v(TAG, "handleSyncHandlerMessage: sync context is active");
-                                }
-                                runStateSyncing();
-                            }
-
-                            // if the above call to runStateSyncing() resulted in the end of a sync,
-                            // check if it is time to start a new sync
-                            if (mActiveSyncContext == null) {
-                                if (isLoggable) {
-                                    Log.v(TAG, "handleSyncHandlerMessage: "
-                                            + "sync context is not active");
-                                }
-                                runStateIdle();
-                            }
+                            nextPendingSyncTime = maybeStartNextSyncLocked();
                         } finally {
                             mHandleAlarmWakeLock.release();
                         }
@@ -1490,19 +1458,14 @@
                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
                             Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_CHECK_ALARMS");
                         }
-                        // we do all the work for this case in the finally block
+                        nextPendingSyncTime = maybeStartNextSyncLocked();
                         break;
                 }
             } finally {
-                final boolean isSyncInProgress = mActiveSyncContext != null;
-                if (!isSyncInProgress && mSyncWakeLock != null) {
-                    mSyncWakeLock.release();
-                    mSyncWakeLock = null;
-                }
-                manageSyncNotification();
-                manageErrorNotification();
-                manageSyncAlarm(earliestFuturePollTime);
+                manageSyncNotificationLocked();
+                manageSyncAlarmLocked(earliestFuturePollTime, nextPendingSyncTime);
                 mSyncTimeTracker.update();
+                mSyncManagerWakeLock.release();
             }
         }
 
@@ -1511,10 +1474,10 @@
          * @return the desired start time of the earliest future  periodic sync operation,
          * in milliseconds since boot
          */
-        private Long scheduleReadyPeriodicSyncs() {
+        private long scheduleReadyPeriodicSyncs() {
             final boolean backgroundDataUsageAllowed =
                     getConnectivityManager().getBackgroundDataSetting();
-            Long earliestFuturePollTime = null;
+            long earliestFuturePollTime = Long.MAX_VALUE;
             if (!backgroundDataUsageAllowed || !mSyncStorageEngine.getMasterSyncAutomatically()) {
                 return earliestFuturePollTime;
             }
@@ -1544,23 +1507,27 @@
                     long nextPollTimeAbsolute = lastPollTimeAbsolute + periodInSeconds * 1000;
                     // if it is ready to run then schedule it and mark it as having been scheduled
                     if (nextPollTimeAbsolute <= nowAbsolute) {
+                        final Pair<Long, Long> backoff =
+                                mSyncStorageEngine.getBackoff(info.account, info.authority);
                         scheduleSyncOperation(
                                 new SyncOperation(info.account, SyncStorageEngine.SOURCE_PERIODIC,
-                                        info.authority, extras, 0 /* delay */));
+                                        info.authority, extras, 0 /* delay */,
+                                        backoff != null ? backoff.first : 0,
+                                        mSyncStorageEngine.getDelayUntilTime(
+                                                info.account, info.authority)));
                         status.setPeriodicSyncTime(i, nowAbsolute);
                     } else {
                         // it isn't ready to run, remember this time if it is earlier than
                         // earliestFuturePollTime
-                        if (earliestFuturePollTime == null
-                                || nextPollTimeAbsolute < earliestFuturePollTime) {
+                        if (nextPollTimeAbsolute < earliestFuturePollTime) {
                             earliestFuturePollTime = nextPollTimeAbsolute;
                         }
                     }
                 }
             }
 
-            if (earliestFuturePollTime == null) {
-                return null;
+            if (earliestFuturePollTime == Long.MAX_VALUE) {
+                return Long.MAX_VALUE;
             }
 
             // convert absolute time to elapsed time
@@ -1570,47 +1537,23 @@
                       : (earliestFuturePollTime - nowAbsolute));
         }
 
-        private void runStateSyncing() {
-            // if the sync timeout has been reached then cancel it
-            ActiveSyncContext activeSyncContext = mActiveSyncContext;
-
-            final long now = SystemClock.elapsedRealtime();
-            if (now > activeSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC) {
-                Pair<SyncOperation, Long> nextOpAndRunTime;
-                synchronized (mSyncQueue) {
-                    nextOpAndRunTime = mSyncQueue.nextOperation();
-                }
-                if (nextOpAndRunTime != null && nextOpAndRunTime.second <= now) {
-                    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 {
-                    activeSyncContext.mTimeoutStartTime = now + MAX_TIME_PER_SYNC;
-                }
-            }
-
-            // no need to schedule an alarm, as that will be done by our caller.
-        }
-
-        private void runStateIdle() {
-            boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
-            if (isLoggable) Log.v(TAG, "runStateIdle");
+        private long maybeStartNextSyncLocked() {
+            final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+            if (isLoggable) Log.v(TAG, "maybeStartNextSync");
 
             // If we aren't ready to run (e.g. the data connection is down), get out.
             if (!mDataConnectionIsConnected) {
                 if (isLoggable) {
-                    Log.v(TAG, "runStateIdle: no data connection, skipping");
+                    Log.v(TAG, "maybeStartNextSync: no data connection, skipping");
                 }
-                return;
+                return Long.MAX_VALUE;
             }
 
             if (mStorageIsLow) {
                 if (isLoggable) {
-                    Log.v(TAG, "runStateIdle: memory low, skipping");
+                    Log.v(TAG, "maybeStartNextSync: memory low, skipping");
                 }
-                return;
+                return Long.MAX_VALUE;
             }
 
             // If the accounts aren't known yet then we aren't ready to run. We will be kicked
@@ -1618,46 +1561,56 @@
             Account[] accounts = mAccounts;
             if (accounts == INITIAL_ACCOUNTS_ARRAY) {
                 if (isLoggable) {
-                    Log.v(TAG, "runStateIdle: accounts not known, skipping");
+                    Log.v(TAG, "maybeStartNextSync: accounts not known, skipping");
                 }
-                return;
+                return Long.MAX_VALUE;
             }
 
             // Otherwise consume SyncOperations from the head of the SyncQueue until one is
             // found that is runnable (not disabled, etc). If that one is ready to run then
             // start it, otherwise just get out.
-            SyncOperation op;
-            int syncableState;
             final boolean backgroundDataUsageAllowed =
                     getConnectivityManager().getBackgroundDataSetting();
             final boolean masterSyncAutomatically = mSyncStorageEngine.getMasterSyncAutomatically();
 
-            synchronized (mSyncQueue) {
-                final long now = SystemClock.elapsedRealtime();
-                while (true) {
-                    Pair<SyncOperation, Long> nextOpAndRunTime = mSyncQueue.nextOperation();
-                    if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) {
-                        if (isLoggable) {
-                            Log.v(TAG, "runStateIdle: no more ready sync operations, returning");
-                        }
-                        return;
-                    }
-                    op = nextOpAndRunTime.first;
+            final long now = SystemClock.elapsedRealtime();
 
-                    // we are either going to run this sync or drop it so go ahead and
-                    // remove it from the queue now
-                    mSyncQueue.remove(op);
+            // will be set to the next time that a sync should be considered for running
+            long nextReadyToRunTime = Long.MAX_VALUE;
+
+            // order the sync queue, dropping syncs that are not allowed
+            ArrayList<SyncOperation> operations = new ArrayList<SyncOperation>();
+            synchronized (mSyncQueue) {
+                if (isLoggable) {
+                    Log.v(TAG, "build the operation array, syncQueue size is "
+                        + mSyncQueue.mOperationsMap.size());
+                }
+                Iterator<SyncOperation> operationIterator =
+                        mSyncQueue.mOperationsMap.values().iterator();
+                while (operationIterator.hasNext()) {
+                    final SyncOperation op = operationIterator.next();
 
                     // drop the sync if the account of this operation no longer exists
                     if (!ArrayUtils.contains(mAccounts, op.account)) {
+                        operationIterator.remove();
+                        mSyncStorageEngine.deleteFromPending(op.pendingOperation);
                         continue;
                     }
 
-
-                    // drop this sync request if it isn't syncable, intializing the sync adapter
-                    // if the syncable state is set to "unknown"
-                    syncableState = mSyncStorageEngine.getIsSyncable(op.account, op.authority);
+                    // drop this sync request if it isn't syncable
+                    int syncableState = mSyncStorageEngine.getIsSyncable(op.account, op.authority);
                     if (syncableState == 0) {
+                        operationIterator.remove();
+                        mSyncStorageEngine.deleteFromPending(op.pendingOperation);
+                        continue;
+                    }
+
+                    // if the next run time is in the future, meaning there are no syncs ready
+                    // to run, return the time
+                    if (op.effectiveRunTime > now) {
+                        if (nextReadyToRunTime > op.effectiveRunTime) {
+                            nextReadyToRunTime = op.effectiveRunTime;
+                        }
                         continue;
                     }
 
@@ -1669,30 +1622,139 @@
                                 || !backgroundDataUsageAllowed
                                 || !mSyncStorageEngine.getSyncAutomatically(
                                        op.account, op.authority))) {
+                        operationIterator.remove();
+                        mSyncStorageEngine.deleteFromPending(op.pendingOperation);
                         continue;
                     }
 
-                    // go ahead and try to sync this syncOperation
-                    break;
-                }
-
-                // We will do this sync. Run it outside of the synchronized block.
-                if (isLoggable) {
-                    Log.v(TAG, "runStateIdle: we are going to sync " + op);
+                    operations.add(op);
                 }
             }
 
-            // convert the op into an initialization sync if the syncable state is "unknown" and
-            // op isn't already an initialization sync. If it is marked syncable then convert
-            // this into a regular sync
-            final boolean initializeIsSet =
-                    op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
-            if (syncableState < 0 && !initializeIsSet) {
-                op.extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
-                op = new SyncOperation(op);
-            } else if (syncableState > 0 && initializeIsSet) {
-                op.extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
-                op = new SyncOperation(op);
+            // find the next operation to dispatch, if one is ready
+            // iterate from the top, keep issuing (while potentially cancelling existing syncs)
+            // until the quotas are filled.
+            // once the quotas are filled iterate once more to find when the next one would be
+            // (also considering pre-emption reasons).
+            if (isLoggable) Log.v(TAG, "sort the candidate operations, size " + operations.size());
+            Collections.sort(operations);
+            if (isLoggable) Log.v(TAG, "dispatch all ready sync operations");
+            for (int i = 0, N = operations.size(); i < N; i++) {
+                final SyncOperation candidate = operations.get(i);
+                final boolean candidateIsInitialization = candidate.isInitialization();
+
+                int numInit = 0;
+                int numRegular = 0;
+                ActiveSyncContext conflict = null;
+                ActiveSyncContext longRunning = null;
+                ActiveSyncContext toReschedule = null;
+
+                for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
+                    final SyncOperation activeOp = activeSyncContext.mSyncOperation;
+                    if (activeOp.isInitialization()) {
+                        numInit++;
+                    } else {
+                        numRegular++;
+                    }
+                    if (activeOp.account.type.equals(candidate.account.type)
+                            && activeOp.authority.equals(candidate.authority)) {
+                        conflict = activeSyncContext;
+                        // don't break out since we want to do a full count of the varieties
+                    } else {
+                        if (candidateIsInitialization == activeOp.isInitialization()
+                                && activeSyncContext.mStartTime + MAX_TIME_PER_SYNC < now) {
+                            longRunning = activeSyncContext;
+                            // don't break out since we want to do a full count of the varieties
+                        }
+                    }
+                }
+
+                if (isLoggable) {
+                    Log.v(TAG, "candidate " + (i + 1) + " of " + N + ": " + candidate);
+                    Log.v(TAG, "  numActiveInit=" + numInit + ", numActiveRegular=" + numRegular);
+                    Log.v(TAG, "  longRunning: " + longRunning);
+                    Log.v(TAG, "  conflict: " + conflict);
+                }
+
+                if (conflict != null) {
+                    if (candidateIsInitialization && !conflict.mSyncOperation.isInitialization()
+                            && numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS) {
+                        toReschedule = conflict;
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "canceling and rescheduling sync since an initialization "
+                                    + "takes higher priority, " + conflict);
+                        }
+                    } else if (candidate.expedited && !conflict.mSyncOperation.expedited
+                            && (candidateIsInitialization
+                                == conflict.mSyncOperation.isInitialization())) {
+                        toReschedule = conflict;
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "canceling and rescheduling sync since an expedited "
+                                    + "takes higher priority, " + conflict);
+                        }
+                    } else {
+                        continue;
+                    }
+                } else {
+                    final boolean roomAvailable = candidateIsInitialization 
+                            ? numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS 
+                            : numRegular < MAX_SIMULTANEOUS_REGULAR_SYNCS;
+                    if (roomAvailable) {
+                        // dispatch candidate
+                    } else if (longRunning != null
+                            && (candidateIsInitialization
+                                == longRunning.mSyncOperation.isInitialization())) {
+                        toReschedule = longRunning;
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "canceling and rescheduling sync since it ran roo long, "
+                                    + longRunning);
+                        }
+                    } else {
+                        continue;
+                    }
+                }
+
+                if (toReschedule != null) {
+                    runSyncFinishedOrCanceledLocked(null, toReschedule);
+                    scheduleSyncOperation(toReschedule.mSyncOperation);
+                }
+    
+                synchronized (mSyncQueue){
+                    mSyncQueue.remove(candidate);
+                }
+                dispatchSyncOperation(candidate);
+            }
+
+            return nextReadyToRunTime;
+     }
+
+        private boolean dispatchSyncOperation(SyncOperation op) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "maybeStartNextSync: we are going to sync " + op);
+                Log.v(TAG, "num active syncs: " + mActiveSyncContexts.size());
+                for (ActiveSyncContext syncContext : mActiveSyncContexts) {
+                    Log.v(TAG, syncContext.toString());
+                }
+            }
+
+            // if this is an initialization sync and there is already a sync running with
+            // the same account type and authority cancel that sync before starting this one
+            // since otherwise the syncadapter will likely reject this request
+            if (op.isInitialization()) {
+                Iterator<ActiveSyncContext> iterator = mActiveSyncContexts.iterator();
+                while (iterator.hasNext()) {
+                    ActiveSyncContext syncContext = iterator.next();
+                    if (!syncContext.mSyncOperation.isInitialization()
+                            && syncContext.mSyncOperation.account.type.equals(op.account.type)
+                            && syncContext.mSyncOperation.authority.equals(op.authority)) {
+                        Log.d(TAG, "canceling and rescheduling " + syncContext.mSyncOperation
+                                + " since we are about to start a sync that used the "
+                                + "same sync adapter, " + op);
+                        iterator.remove();
+                        runSyncFinishedOrCanceledLocked(null, syncContext);
+                        scheduleSyncOperation(syncContext.mSyncOperation);
+                    }
+                }
             }
 
             // connect to the sync adapter
@@ -1703,79 +1765,70 @@
                 Log.d(TAG, "can't find a sync adapter for " + syncAdapterType
                         + ", removing settings for it");
                 mSyncStorageEngine.removeAuthority(op.account, op.authority);
-                runStateIdle();
-                return;
+                return false;
             }
 
             ActiveSyncContext activeSyncContext =
-                    new ActiveSyncContext(op, insertStartSyncEvent(op));
-            mActiveSyncContext = activeSyncContext;
+                    new ActiveSyncContext(op, insertStartSyncEvent(op), syncAdapterInfo.uid);
+            activeSyncContext.mSyncInfo = mSyncStorageEngine.addActiveSync(activeSyncContext);
+            mActiveSyncContexts.add(activeSyncContext);
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Log.v(TAG, "runStateIdle: setting mActiveSyncContext to " + mActiveSyncContext);
+                Log.v(TAG, "dispatchSyncOperation: starting " + activeSyncContext);
             }
-            mSyncStorageEngine.setActiveSync(mActiveSyncContext);
             if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo)) {
                 Log.e(TAG, "Bind attempt failed to " + syncAdapterInfo);
-                mActiveSyncContext.close();
-                mActiveSyncContext = null;
-                mSyncStorageEngine.setActiveSync(mActiveSyncContext);
-                runStateIdle();
-                return;
+                closeActiveSyncContext(activeSyncContext);
+                return false;
             }
 
-            // Find the wakelock for this account and authority and store it in mSyncWakeLock.
-            // Be sure to release the previous wakelock so that we don't end up with it being
-            // held until it is used again.
-            // There are a couple tricky things about this code:
-            // - make sure that we acquire the new wakelock before releasing the old one,
-            //   otherwise the device might go to sleep as soon as we release it.
-            // - since we use non-reference counted wakelocks we have to be sure not to do
-            //   the release if the wakelock didn't change. Othewise we would do an
-            //   acquire followed by a release on the same lock, resulting in no lock
-            //   being held.
-            PowerManager.WakeLock oldWakeLock = mSyncWakeLock;
-            try {
-                mSyncWakeLock = getSyncWakeLock(op.account.type, op.authority);
-				mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterInfo.uid));
-                mSyncWakeLock.acquire();
-            } finally {
-                if (oldWakeLock != null && oldWakeLock != mSyncWakeLock) {
-                    oldWakeLock.release();
-                }
-            }
-
-            // no need to schedule an alarm, as that will be done by our caller.
-
-            // the next step will occur when we get either a timeout or a
-            // MESSAGE_SERVICE_CONNECTED or MESSAGE_SERVICE_DISCONNECTED message
+            return true;
         }
 
-        private void runBoundToSyncAdapter(ISyncAdapter syncAdapter) {
-            mActiveSyncContext.mSyncAdapter = syncAdapter;
-            final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
+        private void runBoundToSyncAdapter(ActiveSyncContext activeSyncContext,
+              ISyncAdapter syncAdapter) {
+            activeSyncContext.mSyncAdapter = syncAdapter;
+            final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
             try {
-                syncAdapter.startSync(mActiveSyncContext, syncOperation.authority,
+                syncAdapter.startSync(activeSyncContext, syncOperation.authority,
                         syncOperation.account, syncOperation.extras);
             } catch (RemoteException remoteExc) {
-                Log.d(TAG, "runStateIdle: caught a RemoteException, rescheduling", remoteExc);
-                mActiveSyncContext.close();
-                mActiveSyncContext = null;
-                mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+                Log.d(TAG, "maybeStartNextSync: caught a RemoteException, rescheduling", remoteExc);
+                closeActiveSyncContext(activeSyncContext);
                 increaseBackoffSetting(syncOperation);
                 scheduleSyncOperation(new SyncOperation(syncOperation));
             } catch (RuntimeException exc) {
-                mActiveSyncContext.close();
-                mActiveSyncContext = null;
-                mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+                closeActiveSyncContext(activeSyncContext);
                 Log.e(TAG, "Caught RuntimeException while starting the sync " + syncOperation, exc);
             }
         }
 
-        private void runSyncFinishedOrCanceled(SyncResult syncResult) {
+        private void cancelActiveSyncLocked(Account account, String authority) {
+            ArrayList<ActiveSyncContext> activeSyncs =
+                    new ArrayList<ActiveSyncContext>(mActiveSyncContexts);
+            for (ActiveSyncContext activeSyncContext : activeSyncs) {
+                if (activeSyncContext != null) {
+                    // if an authority was specified then only cancel the sync if it matches
+                    if (account != null) {
+                        if (!account.equals(activeSyncContext.mSyncOperation.account)) {
+                            return;
+                        }
+                    }
+                    // if an account was specified then only cancel the sync if it matches
+                    if (authority != null) {
+                        if (!authority.equals(activeSyncContext.mSyncOperation.authority)) {
+                            return;
+                        }
+                    }
+                    runSyncFinishedOrCanceledLocked(null /* no result since this is a cancel */,
+                            activeSyncContext);
+                }
+            }
+        }
+
+        private void runSyncFinishedOrCanceledLocked(SyncResult syncResult,
+                ActiveSyncContext activeSyncContext) {
             boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
-            final ActiveSyncContext activeSyncContext = mActiveSyncContext;
-            mActiveSyncContext = null;
-            mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+            closeActiveSyncContext(activeSyncContext);
 
             final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
 
@@ -1795,16 +1848,6 @@
                     // TODO: set these correctly when the SyncResult is extended to include it
                     downstreamActivity = 0;
                     upstreamActivity = 0;
-                    clearBackoffSetting(syncOperation);
-                    // if this was an initialization sync and the sync adapter is now
-                    // marked syncable then reschedule the sync. The next time it runs it
-                    // will be made into a regular sync.
-                    if (syncOperation.extras.getBoolean(
-                                ContentResolver.SYNC_EXTRAS_INITIALIZE, false)
-                            && mSyncStorageEngine.getIsSyncable(
-                                syncOperation.account, syncOperation.authority) > 0) {
-                        scheduleSyncOperation(new SyncOperation(syncOperation));
-                    }
                 } else {
                     Log.d(TAG, "failed sync operation " + syncOperation + ", " + syncResult);
                     // the operation failed so increase the backoff time
@@ -1839,8 +1882,6 @@
             stopSyncEvent(activeSyncContext.mHistoryRowId, syncOperation, historyMessage,
                     upstreamActivity, downstreamActivity, elapsedTime);
 
-            activeSyncContext.close();
-
             if (syncResult != null && syncResult.tooManyDeletions) {
                 installHandleTooManyDeletesNotification(syncOperation.account,
                         syncOperation.authority, syncResult.stats.numDeletes);
@@ -1851,11 +1892,18 @@
 
             if (syncResult != null && syncResult.fullSyncRequested) {
                 scheduleSyncOperation(new SyncOperation(syncOperation.account,
-                        syncOperation.syncSource, syncOperation.authority, new Bundle(), 0));
+                        syncOperation.syncSource, syncOperation.authority, new Bundle(), 0,
+                        syncOperation.backoff, syncOperation.delayUntil));
             }
             // no need to schedule an alarm, as that will be done by our caller.
         }
 
+        private void closeActiveSyncContext(ActiveSyncContext activeSyncContext) {
+            activeSyncContext.close();
+            mActiveSyncContexts.remove(activeSyncContext);
+            mSyncStorageEngine.removeActiveSync(activeSyncContext.mSyncInfo);
+        }
+
         /**
          * Convert the error-containing SyncResult into the Sync.History error number. Since
          * the SyncResult may indicate multiple errors at once, this method just returns the
@@ -1885,11 +1933,11 @@
             throw new IllegalStateException("we are not in an error state, " + syncResult);
         }
 
-        private void manageSyncNotification() {
+        private void manageSyncNotificationLocked() {
             boolean shouldCancel;
             boolean shouldInstall;
 
-            if (mActiveSyncContext == null) {
+            if (mActiveSyncContexts.isEmpty()) {
                 mSyncNotificationInfo.startTime = null;
 
                 // we aren't syncing. if the notification is active then remember that we need
@@ -1898,34 +1946,38 @@
                 shouldInstall = false;
             } else {
                 // we are syncing
-                final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
-
                 final long now = SystemClock.elapsedRealtime();
                 if (mSyncNotificationInfo.startTime == null) {
                     mSyncNotificationInfo.startTime = now;
                 }
 
-                // cancel the notification if it is up and the authority or account is wrong
-                shouldCancel = mSyncNotificationInfo.isActive &&
-                        (!syncOperation.authority.equals(mSyncNotificationInfo.authority)
-                        || !syncOperation.account.equals(mSyncNotificationInfo.account));
-
-                // there are four cases:
-                // - the notification is up and there is no change: do nothing
-                // - the notification is up but we should cancel since it is stale:
-                //   need to install
+                // there are three cases:
+                // - the notification is up: do nothing
                 // - the notification is not up but it isn't time yet: don't install
                 // - the notification is not up and it is time: need to install
 
                 if (mSyncNotificationInfo.isActive) {
-                    shouldInstall = shouldCancel;
+                    shouldInstall = shouldCancel = false;
                 } else {
+                    // it isn't currently up, so there is nothing to cancel
+                    shouldCancel = false;
+
                     final boolean timeToShowNotification =
                             now > mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY;
-                    // show the notification immediately if this is a manual sync
-                    final boolean manualSync = syncOperation.extras
-                            .getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
-                    shouldInstall = timeToShowNotification || manualSync;
+                    if (timeToShowNotification) {
+                        shouldInstall = true;
+                    } else {
+                        // show the notification immediately if this is a manual sync
+                        shouldInstall = false;
+                        for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
+                            final boolean manualSync = activeSyncContext.mSyncOperation.extras
+                                    .getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
+                            if (manualSync) {
+                                shouldInstall = true;
+                                break;
+                            }
+                        }
+                    }
                 }
             }
 
@@ -1936,94 +1988,82 @@
             }
 
             if (shouldInstall) {
-                SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
                 mNeedSyncActiveNotification = true;
                 sendSyncStateIntent();
                 mSyncNotificationInfo.isActive = true;
-                mSyncNotificationInfo.account = syncOperation.account;
-                mSyncNotificationInfo.authority = syncOperation.authority;
             }
         }
 
-        /**
-         * Check if there were any long-lasting errors, if so install the error notification,
-         * otherwise cancel the error notification.
-         */
-        private void manageErrorNotification() {
-            //
-            long when = mSyncStorageEngine.getInitialSyncFailureTime();
-            if ((when > 0) && (when + ERROR_NOTIFICATION_DELAY_MS < System.currentTimeMillis())) {
-                if (!mErrorNotificationInstalled) {
-                    mNeedSyncErrorNotification = true;
-                    sendSyncStateIntent();
-                }
-                mErrorNotificationInstalled = true;
-            } else {
-                if (mErrorNotificationInstalled) {
-                    mNeedSyncErrorNotification = false;
-                    sendSyncStateIntent();
-                }
-                mErrorNotificationInstalled = false;
-            }
-        }
-
-        private void manageSyncAlarm(Long earliestFuturePollElapsedTime) {
+        private void manageSyncAlarmLocked(long nextPeriodicEventElapsedTime,
+                long nextPendingEventElapsedTime) {
             // in each of these cases the sync loop will be kicked, which will cause this
             // method to be called again
             if (!mDataConnectionIsConnected) return;
             if (mStorageIsLow) return;
 
-            final long now = SystemClock.elapsedRealtime();
+            // When the status bar notification should be raised
+            final long notificationTime =
+                    (!mSyncHandler.mSyncNotificationInfo.isActive
+                            && mSyncHandler.mSyncNotificationInfo.startTime != null)
+                            ? mSyncHandler.mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY
+                            : Long.MAX_VALUE;
 
-            // 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;
-            ActiveSyncContext activeSyncContext = mActiveSyncContext;
-            if (activeSyncContext == null) {
-                synchronized (mSyncQueue) {
-                    final Pair<SyncOperation, Long> candidate = mSyncQueue.nextOperation();
-                    if (earliestFuturePollElapsedTime == null && candidate == null) {
-                        alarmTime = null;
-                    } else if (earliestFuturePollElapsedTime == null) {
-                        alarmTime = candidate.second;
-                    } else if (candidate == null) {
-                        alarmTime = earliestFuturePollElapsedTime;
-                    } else {
-                        alarmTime = Math.min(earliestFuturePollElapsedTime, candidate.second);
-                    }
+            // When we should consider canceling an active sync
+            long earliestTimeoutTime = Long.MAX_VALUE;
+            for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
+                final long currentSyncTimeoutTime =
+                        currentSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC;
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "manageSyncAlarm: active sync, mTimeoutStartTime + MAX is "
+                            + currentSyncTimeoutTime);
                 }
-            } else {
-                final long notificationTime =
-                        mSyncHandler.mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY;
-                final long timeoutTime =
-                        mActiveSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC;
-                if (mSyncHandler.mSyncNotificationInfo.isActive) {
-                    alarmTime = timeoutTime;
-                } else {
-                    alarmTime = Math.min(notificationTime, timeoutTime);
+                if (earliestTimeoutTime > currentSyncTimeoutTime) {
+                    earliestTimeoutTime = currentSyncTimeoutTime;
                 }
             }
 
-            // adjust the alarmTime so that we will wake up when it is time to
-            // install the error notification
-            if (!mErrorNotificationInstalled) {
-                long when = mSyncStorageEngine.getInitialSyncFailureTime();
-                if (when > 0) {
-                    when += ERROR_NOTIFICATION_DELAY_MS;
-                    // convert when fron absolute time to elapsed run time
-                    long delay = when - System.currentTimeMillis();
-                    when = now + delay;
-                    alarmTime = alarmTime != null ? Math.min(alarmTime, when) : when;
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "manageSyncAlarm: notificationTime is " + notificationTime);
+            }
+
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "manageSyncAlarm: earliestTimeoutTime is " + earliestTimeoutTime);
+            }
+
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "manageSyncAlarm: nextPeriodicEventElapsedTime is "
+                        + nextPeriodicEventElapsedTime);
+            }
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "manageSyncAlarm: nextPendingEventElapsedTime is "
+                        + nextPendingEventElapsedTime);
+            }
+
+            long alarmTime = Math.min(notificationTime, earliestTimeoutTime);
+            alarmTime = Math.min(alarmTime, nextPeriodicEventElapsedTime);
+            alarmTime = Math.min(alarmTime, nextPendingEventElapsedTime);
+
+            // Bound the alarm time.
+            final long now = SystemClock.elapsedRealtime();
+            if (alarmTime < now + SYNC_ALARM_TIMEOUT_MIN) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "manageSyncAlarm: the alarmTime is too small, "
+                            + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN));
                 }
+                alarmTime = now + SYNC_ALARM_TIMEOUT_MIN;
+            } else if (alarmTime > now + SYNC_ALARM_TIMEOUT_MAX) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "manageSyncAlarm: the alarmTime is too large, "
+                            + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN));
+                }
+                alarmTime = now + SYNC_ALARM_TIMEOUT_MAX;
             }
 
             // determine if we need to set or cancel the alarm
             boolean shouldSet = false;
             boolean shouldCancel = false;
             final boolean alarmIsActive = mAlarmScheduleTime != null;
-            final boolean needAlarm = alarmTime != null;
+            final boolean needAlarm = alarmTime != Long.MAX_VALUE;
             if (needAlarm) {
                 if (!alarmIsActive || alarmTime < mAlarmScheduleTime) {
                     shouldSet = true;
@@ -2035,6 +2075,11 @@
             // set or cancel the alarm as directed
             ensureAlarmService();
             if (shouldSet) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "requesting that the alarm manager wake us up at elapsed time "
+                            + alarmTime + ", now is " + now + ", " + ((alarmTime - now) / 1000)
+                            + " secs from now");
+                }
                 mAlarmScheduleTime = alarmTime;
                 mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime,
                         mSyncAlarmIntent);
@@ -2048,7 +2093,7 @@
             Intent syncStateIntent = new Intent(Intent.ACTION_SYNC_STATE_CHANGED);
             syncStateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
             syncStateIntent.putExtra("active", mNeedSyncActiveNotification);
-            syncStateIntent.putExtra("failing", mNeedSyncErrorNotification);
+            syncStateIntent.putExtra("failing", false);
             mContext.sendBroadcast(syncStateIntent);
         }
 
@@ -2137,4 +2182,13 @@
                     resultMessage, downstreamActivity, upstreamActivity);
         }
     }
+
+    private boolean isSyncStillActive(ActiveSyncContext activeSyncContext) {
+        for (ActiveSyncContext sync : mActiveSyncContexts) {
+            if (sync == activeSyncContext) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/core/java/android/content/SyncOperation.java b/core/java/android/content/SyncOperation.java
index b0160885..3d7f3fbf 100644
--- a/core/java/android/content/SyncOperation.java
+++ b/core/java/android/content/SyncOperation.java
@@ -33,9 +33,12 @@
     public long earliestRunTime;
     public boolean expedited;
     public SyncStorageEngine.PendingOperation pendingOperation;
+    public Long backoff;
+    public long delayUntil;
+    public long effectiveRunTime;
 
     public SyncOperation(Account account, int source, String authority, Bundle extras,
-            long delayInMs) {
+            long delayInMs, long backoff, long delayUntil) {
         this.account = account;
         this.syncSource = source;
         this.authority = authority;
@@ -48,6 +51,8 @@
         removeFalseExtra(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS);
         removeFalseExtra(ContentResolver.SYNC_EXTRAS_EXPEDITED);
         removeFalseExtra(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS);
+        this.delayUntil = delayUntil;
+        this.backoff = backoff;
         final long now = SystemClock.elapsedRealtime();
         if (delayInMs < 0) {
             this.expedited = true;
@@ -56,6 +61,7 @@
             this.expedited = false;
             this.earliestRunTime = now + delayInMs;
         }
+        updateEffectiveRunTime();
         this.key = toKey();
     }
 
@@ -72,49 +78,78 @@
         this.extras = new Bundle(other.extras);
         this.expedited = other.expedited;
         this.earliestRunTime = SystemClock.elapsedRealtime();
+        this.backoff = other.backoff;
+        this.delayUntil = other.delayUntil;
+        this.updateEffectiveRunTime();
         this.key = toKey();
     }
 
     public String toString() {
+        return dump(true);
+    }
+
+    public String dump(boolean useOneLine) {
         StringBuilder sb = new StringBuilder();
-        sb.append("authority: ").append(authority);
-        sb.append(" account: ").append(account);
-        sb.append(" extras: ");
-        extrasToStringBuilder(extras, sb, false /* asKey */);
-        sb.append(" syncSource: ").append(syncSource);
-        sb.append(" when: ").append(earliestRunTime);
-        sb.append(" expedited: ").append(expedited);
+        sb.append(account.name);
+        sb.append(" (" + account.type + ")");
+        sb.append(", " + authority);
+        sb.append(", ");
+        sb.append(SyncStorageEngine.SOURCES[syncSource]);
+        sb.append(", earliestRunTime " + earliestRunTime);
+        if (expedited) {
+            sb.append(", EXPEDITED");
+        }
+        if (!useOneLine && !extras.keySet().isEmpty()) {
+            sb.append("\n    ");
+            extrasToStringBuilder(extras, sb);
+        }
         return sb.toString();
     }
 
+    public boolean isInitialization() {
+        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
+    }
+
+    public boolean ignoreBackoff() {
+        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
+    }
+
     private String toKey() {
         StringBuilder sb = new StringBuilder();
         sb.append("authority: ").append(authority);
-	sb.append(" account {name=" + account.name + ", type=" + account.type + "}");
+    	sb.append(" account {name=" + account.name + ", type=" + account.type + "}");
         sb.append(" extras: ");
-        extrasToStringBuilder(extras, sb, true /* asKey */);
+        extrasToStringBuilder(extras, sb);
         return sb.toString();
     }
 
-    public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb, boolean asKey) {
+    public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
         sb.append("[");
         for (String key : bundle.keySet()) {
-            // if we are writing this as a key don't consider whether this
-            // is an initialization sync or not when computing the key since
-            // we set this flag appropriately when dispatching the sync request.
-            if (asKey && ContentResolver.SYNC_EXTRAS_INITIALIZE.equals(key)) {
-                continue;
-            }
             sb.append(key).append("=").append(bundle.get(key)).append(" ");
         }
         sb.append("]");
     }
 
+    public void updateEffectiveRunTime() {
+        effectiveRunTime = ignoreBackoff()
+                ? earliestRunTime
+                : Math.max(
+                    Math.max(earliestRunTime, delayUntil),
+                    backoff);
+    }
+
     public int compareTo(Object o) {
         SyncOperation other = (SyncOperation)o;
-        if (earliestRunTime == other.earliestRunTime) {
+
+        if (expedited != other.expedited) {
+            return expedited ? -1 : 1;
+        }
+
+        if (effectiveRunTime == other.effectiveRunTime) {
             return 0;
         }
-        return (earliestRunTime < other.earliestRunTime) ? -1 : 1;
+
+        return effectiveRunTime < other.effectiveRunTime ? -1 : 1;
     }
 }
diff --git a/core/java/android/content/SyncQueue.java b/core/java/android/content/SyncQueue.java
index 28baa0d..f826147 100644
--- a/core/java/android/content/SyncQueue.java
+++ b/core/java/android/content/SyncQueue.java
@@ -16,17 +16,18 @@
 
 package android.content;
 
-import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
 
+import android.os.SystemClock;
+import android.text.format.DateUtils;
 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.HashMap;
 import java.util.Iterator;
+import java.util.Map;
 
 /**
  *
@@ -38,7 +39,7 @@
 
     // 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 final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap();
 
     public SyncQueue(SyncStorageEngine syncStorageEngine) {
         mSyncStorageEngine = syncStorageEngine;
@@ -47,8 +48,11 @@
         final int N = ops.size();
         for (int i=0; i<N; i++) {
             SyncStorageEngine.PendingOperation op = ops.get(i);
+            final Pair<Long, Long> backoff = syncStorageEngine.getBackoff(op.account, op.authority);
             SyncOperation syncOperation = new SyncOperation(
-                    op.account, op.syncSource, op.authority, op.extras, 0 /* delay */);
+                    op.account, op.syncSource, op.authority, op.extras, 0 /* delay */,
+                    backoff != null ? backoff.first : 0,
+                    syncStorageEngine.getDelayUntilTime(op.account, op.authority));
             syncOperation.expedited = op.expedited;
             syncOperation.pendingOperation = op;
             add(syncOperation, op);
@@ -119,65 +123,26 @@
         }
     }
 
-    /**
-     * Find the operation that should run next. Operations are sorted by their earliestRunTime,
-     * prioritizing first those with a syncable state of "unknown" that aren't retries then
-     * expedited operations.
-     * The earliestRunTime is adjusted by the sync adapter's backoff and delayUntil times, if any.
-     * @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.
-     */
-    public Pair<SyncOperation, Long> nextOperation() {
-        SyncOperation best = null;
-        long bestRunTime = 0;
-        boolean bestSyncableIsUnknownAndNotARetry = false;
+    public void onBackoffChanged(Account account, String providerName, long backoff) {
+        // for each op that matches the account and provider update its
+        // backoff and effectiveStartTime
         for (SyncOperation op : mOperationsMap.values()) {
-            long opRunTime = op.earliestRunTime;
-            if (!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) {
-                Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(op.account, op.authority);
-                long delayUntil = mSyncStorageEngine.getDelayUntilTime(op.account, op.authority);
-                opRunTime = Math.max(
-                        Math.max(opRunTime, delayUntil),
-                        backoff != null ? backoff.first : 0);
-            }
-            // we know a sync is a retry if the intialization flag is set, since that will only
-            // be set by the sync dispatching code, thus if it is set it must have already been
-            // dispatched
-            final boolean syncableIsUnknownAndNotARetry =
-                    !op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)
-                    && mSyncStorageEngine.getIsSyncable(op.account, op.authority) < 0;
-            // if the unsyncable state differs, make the current the best if it is unsyncable
-            // else, if the expedited state differs, make the current the best if it is expedited
-            // else, make the current the best if it is earlier than the best
-            if (best == null
-                    || ((bestSyncableIsUnknownAndNotARetry == syncableIsUnknownAndNotARetry)
-                        ? (best.expedited == op.expedited
-                           ? opRunTime < bestRunTime
-                           : op.expedited)
-                        : syncableIsUnknownAndNotARetry)) {
-                best = op;
-                bestSyncableIsUnknownAndNotARetry = syncableIsUnknownAndNotARetry;
-                bestRunTime = opRunTime;
+            if (op.account.equals(account) && op.authority.equals(providerName)) {
+                op.backoff = backoff;
+                op.updateEffectiveRunTime();
             }
         }
-        if (best == null) {
-            return null;
-        }
-        return Pair.create(best, bestRunTime);
     }
 
-    /**
-     * 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 Pair<SyncOperation, Long> nextReadyToRun(long now) {
-        Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation();
-        if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) {
-            return null;
+    public void onDelayUntilTimeChanged(Account account, String providerName, long delayUntil) {
+        // for each op that matches the account and provider update its
+        // delayUntilTime and effectiveStartTime
+        for (SyncOperation op : mOperationsMap.values()) {
+            if (op.account.equals(account) && op.authority.equals(providerName)) {
+                op.delayUntil = delayUntil;
+                op.updateEffectiveRunTime();
+            }
         }
-        return nextOpAndRunTime;
     }
 
     public void remove(Account account, String authority) {
@@ -200,9 +165,17 @@
     }
 
     public void dump(StringBuilder sb) {
+        final long now = SystemClock.elapsedRealtime();
         sb.append("SyncQueue: ").append(mOperationsMap.size()).append(" operation(s)\n");
         for (SyncOperation operation : mOperationsMap.values()) {
-            sb.append(operation).append("\n");
+            sb.append("  ");
+            if (operation.effectiveRunTime <= now) {
+                sb.append("READY");
+            } else {
+                sb.append(DateUtils.formatElapsedTime((operation.effectiveRunTime - now) / 1000));
+            }
+            sb.append(" - ");
+            sb.append(operation.dump(false)).append("\n");
         }
     }
 }
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index 6413cec..487f6ce 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -229,7 +229,7 @@
     private final ArrayList<PendingOperation> mPendingOperations =
             new ArrayList<PendingOperation>();
 
-    private SyncInfo mCurrentSync;
+    private final ArrayList<SyncInfo> mCurrentSyncs = new ArrayList<SyncInfo>();
 
     private final SparseArray<SyncStatusInfo> mSyncStatus =
             new SparseArray<SyncStatusInfo>();
@@ -690,23 +690,12 @@
 
     /**
      * Returns true if there is currently a sync operation for the given
-     * account or authority in the pending list, or actively being processed.
+     * account or authority actively being processed.
      */
     public boolean isSyncActive(Account account, String authority) {
         synchronized (mAuthorities) {
-            int i = mPendingOperations.size();
-            while (i > 0) {
-                i--;
-                // TODO(fredq): this probably shouldn't be considering
-                // pending operations.
-                PendingOperation op = mPendingOperations.get(i);
-                if (op.account.equals(account) && op.authority.equals(authority)) {
-                    return true;
-                }
-            }
-
-            if (mCurrentSync != null) {
-                AuthorityInfo ainfo = getAuthority(mCurrentSync.authorityId);
+            for (SyncInfo syncInfo : mCurrentSyncs) {
+                AuthorityInfo ainfo = getAuthority(syncInfo.authorityId);
                 if (ainfo != null && ainfo.account.equals(account)
                         && ainfo.authority.equals(authority)) {
                     return true;
@@ -887,40 +876,47 @@
     }
 
     /**
-     * Called when the currently active sync is changing (there can only be
-     * one at a time).  Either supply a valid ActiveSyncContext with information
-     * about the sync, or null to stop the currently active sync.
+     * Called when a sync is starting. Supply a valid ActiveSyncContext with information
+     * about the sync.
      */
-    public void setActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
+    public SyncInfo addActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
+        final SyncInfo syncInfo;
         synchronized (mAuthorities) {
-            if (activeSyncContext != null) {
-                if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG, "setActiveSync: account="
-                        + activeSyncContext.mSyncOperation.account
-                        + " auth=" + activeSyncContext.mSyncOperation.authority
-                        + " src=" + activeSyncContext.mSyncOperation.syncSource
-                        + " extras=" + activeSyncContext.mSyncOperation.extras);
-                }
-                if (mCurrentSync != null) {
-                    Log.w(TAG, "setActiveSync called with existing active sync!");
-                }
-                AuthorityInfo authority = getAuthorityLocked(
-                        activeSyncContext.mSyncOperation.account,
-                        activeSyncContext.mSyncOperation.authority,
-                        "setActiveSync");
-                if (authority == null) {
-                    return;
-                }
-                mCurrentSync = new SyncInfo(authority.ident,
-                        authority.account, authority.authority,
-                        activeSyncContext.mStartTime);
-            } else {
-                if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "setActiveSync: null");
-                mCurrentSync = null;
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "setActiveSync: account="
+                    + activeSyncContext.mSyncOperation.account
+                    + " auth=" + activeSyncContext.mSyncOperation.authority
+                    + " src=" + activeSyncContext.mSyncOperation.syncSource
+                    + " extras=" + activeSyncContext.mSyncOperation.extras);
             }
+            AuthorityInfo authority = getOrCreateAuthorityLocked(
+                    activeSyncContext.mSyncOperation.account,
+                    activeSyncContext.mSyncOperation.authority,
+                    -1 /* assign a new identifier if creating a new authority */,
+                    true /* write to storage if this results in a change */);
+            syncInfo = new SyncInfo(authority.ident,
+                    authority.account, authority.authority,
+                    activeSyncContext.mStartTime);
+            mCurrentSyncs.add(syncInfo);
         }
 
-        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE);
+        reportActiveChange();
+        return syncInfo;
+    }
+
+    /**
+     * Called to indicate that a previously active sync is no longer active.
+     */
+    public void removeActiveSync(SyncInfo syncInfo) {
+        synchronized (mAuthorities) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "removeActiveSync: account="
+                        + syncInfo.account + " auth=" + syncInfo.authority);
+            }
+            mCurrentSyncs.remove(syncInfo);
+        }
+
+        reportActiveChange();
     }
 
     /**
@@ -1095,10 +1091,26 @@
      * Return the currently active sync information, or null if there is no
      * active sync.  Note that the returned object is the real, live active
      * sync object, so be careful what you do with it.
+     * <p>
+     * Since multiple concurrent syncs are now supported you should use
+     * {@link #getCurrentSyncs()} to get the accurate list of current syncs.
+     * This method returns the first item from the list of current syncs
+     * or null if there are none.
+     * @deprecated use {@link #getCurrentSyncs()}
      */
     public SyncInfo getCurrentSync() {
         synchronized (mAuthorities) {
-            return mCurrentSync;
+            return !mCurrentSyncs.isEmpty() ? mCurrentSyncs.get(0) : null;
+        }
+    }
+
+    /**
+     * Return a list of the currently active syncs. Note that the returned items are the
+     * real, live active sync objects, so be careful what you do with it.
+     */
+    public List<SyncInfo> getCurrentSyncs() {
+        synchronized (mAuthorities) {
+            return new ArrayList<SyncInfo>(mCurrentSyncs);
         }
     }
 
diff --git a/core/java/android/net/LinkAddress.aidl b/core/java/android/net/LinkAddress.aidl
new file mode 100644
index 0000000..e7d8646
--- /dev/null
+++ b/core/java/android/net/LinkAddress.aidl
@@ -0,0 +1,21 @@
+/**
+ *
+ * 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.net;
+
+parcelable LinkAddress;
+
diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java
new file mode 100644
index 0000000..cb302da
--- /dev/null
+++ b/core/java/android/net/LinkAddress.java
@@ -0,0 +1,149 @@
+/*
+ * 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.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Identifies an address of a network link
+ * @hide
+ */
+public class LinkAddress implements Parcelable {
+    /**
+     * IPv4 or IPv6 address.
+     */
+    private final InetAddress address;
+
+    /**
+     * Network prefix
+     */
+    private final int prefix;
+
+    public LinkAddress(InetAddress address, InetAddress mask) {
+        this.address = address;
+        this.prefix = computeprefix(mask);
+    }
+
+    public LinkAddress(InetAddress address, int prefix) {
+        this.address = address;
+        this.prefix = prefix;
+    }
+
+    public LinkAddress(InterfaceAddress interfaceAddress) {
+        this.address = interfaceAddress.getAddress();
+        this.prefix = interfaceAddress.getNetworkPrefixLength();
+    }
+
+    private static int computeprefix(InetAddress mask) {
+        int count = 0;
+        for (byte b : mask.getAddress()) {
+            for (int i = 0; i < 8; ++i) {
+                if ((b & (1 << i)) != 0) {
+                    ++count;
+                }
+            }
+        }
+        return count;
+    }
+
+    @Override
+    public String toString() {
+        return (address == null ? "" : (address.getHostAddress() + "/" + prefix));
+    }
+
+    /**
+     * Compares this {@code LinkAddress} instance against the specified address
+     * in {@code obj}. Two addresses are equal if their InetAddress and prefix
+     * are equal
+     *
+     * @param obj the object to be tested for equality.
+     * @return {@code true} if both objects are equal, {@code false} otherwise.
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof LinkAddress)) {
+            return false;
+        }
+        LinkAddress linkAddress = (LinkAddress) obj;
+        return this.address.equals(linkAddress.address) &&
+            this.prefix == linkAddress.prefix;
+    }
+
+    /**
+     * Returns the InetAddress for this address.
+     */
+    public InetAddress getAddress() {
+        return address;
+    }
+
+    /**
+     * Get network prefix length
+     */
+    public int getNetworkPrefix() {
+        return prefix;
+    }
+
+    /**
+     * Implement the Parcelable interface
+     * @hide
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Implement the Parcelable interface.
+     * @hide
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        if (address != null) {
+            dest.writeByte((byte)1);
+            dest.writeByteArray(address.getAddress());
+            dest.writeInt(prefix);
+        } else {
+            dest.writeByte((byte)0);
+        }
+    }
+
+    /**
+     * Implement the Parcelable interface.
+     * @hide
+     */
+    public static final Creator<LinkAddress> CREATOR =
+        new Creator<LinkAddress>() {
+            public LinkAddress createFromParcel(Parcel in) {
+                InetAddress address = null;
+                int prefix = 0;
+                if (in.readByte() == 1) {
+                    try {
+                        address = InetAddress.getByAddress(in.createByteArray());
+                        prefix = in.readInt();
+                    } catch (UnknownHostException e) { }
+                }
+                return new LinkAddress(address, prefix);
+            }
+
+            public LinkAddress[] newArray(int size) {
+                return new LinkAddress[size];
+            }
+        };
+}
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index f411eac..f1545ea 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -36,8 +36,8 @@
  */
 public class LinkProperties implements Parcelable {
 
-    private NetworkInterface mIface;
-    private Collection<InetAddress> mAddresses;
+    String mIfaceName;
+    private Collection<LinkAddress> mLinkAddresses;
     private Collection<InetAddress> mDnses;
     private InetAddress mGateway;
     private ProxyProperties mHttpProxy;
@@ -49,34 +49,42 @@
     // copy constructor instead of clone
     public LinkProperties(LinkProperties source) {
         if (source != null) {
-            mIface = source.getInterface();
-            mAddresses = source.getAddresses();
+            mIfaceName = source.getInterfaceName();
+            mLinkAddresses = source.getLinkAddresses();
             mDnses = source.getDnses();
             mGateway = source.getGateway();
             mHttpProxy = new ProxyProperties(source.getHttpProxy());
         }
     }
 
-    public void setInterface(NetworkInterface iface) {
-        mIface = iface;
-    }
-    public NetworkInterface getInterface() {
-        return mIface;
-    }
-    public String getInterfaceName() {
-        return (mIface == null ? null : mIface.getName());
+    public void setInterfaceName(String iface) {
+        mIfaceName = iface;
     }
 
-    public void addAddress(InetAddress address) {
-        mAddresses.add(address);
+    public String getInterfaceName() {
+        return mIfaceName;
     }
+
     public Collection<InetAddress> getAddresses() {
-        return Collections.unmodifiableCollection(mAddresses);
+        Collection<InetAddress> addresses = new ArrayList<InetAddress>();
+        for (LinkAddress linkAddress : mLinkAddresses) {
+            addresses.add(linkAddress.getAddress());
+        }
+        return Collections.unmodifiableCollection(addresses);
+    }
+
+    public void addLinkAddress(LinkAddress address) {
+        mLinkAddresses.add(address);
+    }
+
+    public Collection<LinkAddress> getLinkAddresses() {
+        return Collections.unmodifiableCollection(mLinkAddresses);
     }
 
     public void addDns(InetAddress dns) {
         mDnses.add(dns);
     }
+
     public Collection<InetAddress> getDnses() {
         return Collections.unmodifiableCollection(mDnses);
     }
@@ -96,8 +104,8 @@
     }
 
     public void clear() {
-        mIface = null;
-        mAddresses = new ArrayList<InetAddress>();
+        mIfaceName = null;
+        mLinkAddresses = new ArrayList<LinkAddress>();
         mDnses = new ArrayList<InetAddress>();
         mGateway = null;
         mHttpProxy = null;
@@ -113,11 +121,11 @@
 
     @Override
     public String toString() {
-        String ifaceName = (mIface == null ? "" : "InterfaceName: " + mIface.getName() + " ");
+        String ifaceName = (mIfaceName == null ? "" : "InterfaceName: " + mIfaceName + " ");
 
-        String ip = "IpAddresses: [";
-        for (InetAddress addr : mAddresses) ip +=  addr.getHostAddress() + ",";
-        ip += "] ";
+        String linkAddresses = "LinkAddresses: [";
+        for (LinkAddress addr : mLinkAddresses) linkAddresses += addr.toString();
+        linkAddresses += "] ";
 
         String dns = "DnsAddresses: [";
         for (InetAddress addr : mDnses) dns += addr.getHostAddress() + ",";
@@ -126,7 +134,7 @@
         String proxy = (mHttpProxy == null ? "" : "HttpProxy: " + mHttpProxy.toString() + " ");
         String gateway = (mGateway == null ? "" : "Gateway: " + mGateway.getHostAddress() + " ");
 
-        return ifaceName + ip + gateway + dns + proxy;
+        return ifaceName + linkAddresses + gateway + dns + proxy;
     }
 
     /**
@@ -135,12 +143,11 @@
      */
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(getInterfaceName());
-        dest.writeInt(mAddresses.size());
-        //TODO: explore an easy alternative to preserve hostname
-        // without doing a lookup
-        for(InetAddress a : mAddresses) {
-            dest.writeByteArray(a.getAddress());
+        dest.writeInt(mLinkAddresses.size());
+        for(LinkAddress linkAddress : mLinkAddresses) {
+            dest.writeParcelable(linkAddress, flags);
         }
+
         dest.writeInt(mDnses.size());
         for(InetAddress d : mDnses) {
             dest.writeByteArray(d.getAddress());
@@ -170,16 +177,14 @@
                 String iface = in.readString();
                 if (iface != null) {
                     try {
-                        netProp.setInterface(NetworkInterface.getByName(iface));
+                        netProp.setInterfaceName(iface);
                     } catch (Exception e) {
                         return null;
                     }
                 }
                 int addressCount = in.readInt();
                 for (int i=0; i<addressCount; i++) {
-                    try {
-                        netProp.addAddress(InetAddress.getByAddress(in.createByteArray()));
-                    } catch (UnknownHostException e) { }
+                    netProp.addLinkAddress((LinkAddress)in.readParcelable(null));
                 }
                 addressCount = in.readInt();
                 for (int i=0; i<addressCount; i++) {
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index ad7289d..3df8ec0 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -45,7 +45,7 @@
 public class MobileDataStateTracker implements NetworkStateTracker {
 
     private static final String TAG = "MobileDataStateTracker";
-    private static final boolean DBG = false;
+    private static final boolean DBG = true;
 
     private Phone.DataState mMobileDataState;
     private ITelephony mPhoneService;
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index a58e70b..2b4f39a 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -775,6 +775,15 @@
     public static native void dumpNativeHeap(FileDescriptor fd);
 
     /**
+      * Returns a count of the extant instances of a class.
+     *
+     * @hide
+     */
+    public static long countInstancesOfClass(Class cls) {
+        return VMDebug.countInstancesOfClass(cls);
+    }
+
+    /**
      * Returns the number of sent transactions from this process.
      * @return The number of sent transactions or -1 if it could not read t.
      */
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index a6c7d9e..a59b2f8 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -893,11 +893,15 @@
         }
     }
 
-    public void switchToHeaderInner(String fragmentName, Bundle args) {
+    public void switchToHeaderInner(String fragmentName, Bundle args, boolean next) {
         getFragmentManager().popBackStack(BACK_STACK_PREFS, POP_BACK_STACK_INCLUSIVE);
         Fragment f = Fragment.instantiate(this, fragmentName, args);
-        getFragmentManager().openTransaction().replace(
-                com.android.internal.R.id.prefs, f).commit();
+        FragmentTransaction transaction = getFragmentManager().openTransaction();
+        transaction.setTransition(next ?
+                FragmentTransaction.TRANSIT_FRAGMENT_NEXT :
+                FragmentTransaction.TRANSIT_FRAGMENT_PREV);
+        transaction.replace(com.android.internal.R.id.prefs, f);
+        transaction.commit();
     }
 
     /**
@@ -909,7 +913,7 @@
      */
     public void switchToHeader(String fragmentName, Bundle args) {
         setSelectedHeader(null);
-        switchToHeaderInner(fragmentName, args);
+        switchToHeaderInner(fragmentName, args, true);
     }
 
     /**
@@ -919,7 +923,8 @@
      * @param header The new header to display.
      */
     public void switchToHeader(Header header) {
-        switchToHeaderInner(header.fragment, header.fragmentArguments);
+        switchToHeaderInner(header.fragment, header.fragmentArguments,
+                mHeaders.indexOf(header) > mHeaders.indexOf(mCurHeader));
         setSelectedHeader(header);
     }
 
@@ -979,7 +984,10 @@
         FragmentTransaction transaction = getFragmentManager().openTransaction();
         startPreferenceFragment(fragment, transaction);
         if (push) {
+            transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
             transaction.addToBackStack(BACK_STACK_PREFS);
+        } else {
+            transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_NEXT);
         }
         transaction.commit();
     }
@@ -1001,6 +1009,7 @@
         FragmentTransaction transaction = getFragmentManager().openTransaction();
         startPreferenceFragment(f, transaction);
         transaction.setBreadCrumbTitle(pref.getTitle());
+        transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
         transaction.addToBackStack(BACK_STACK_PREFS);
         transaction.commit();
         return true;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 63450de..35b806a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -66,6 +66,7 @@
 import com.android.internal.R;
 import com.android.internal.view.menu.MenuBuilder;
 
+import java.lang.ref.WeakReference;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
@@ -8542,7 +8543,7 @@
         if ((privateFlags & PRESSED) != 0) viewStateIndex |= VIEW_STATE_PRESSED;
         if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= VIEW_STATE_ENABLED;
         if (isFocused()) viewStateIndex |= VIEW_STATE_FOCUSED;
-        if ((privateFlags & SELECTED) != 0) viewStateIndex |= VIEW_STATE_PRESSED;
+        if ((privateFlags & SELECTED) != 0) viewStateIndex |= VIEW_STATE_SELECTED;
         if (hasWindowFocus()) viewStateIndex |= VIEW_STATE_WINDOW_FOCUSED;
         if ((privateFlags & ACTIVATED) != 0) viewStateIndex |= VIEW_STATE_ACTIVATED;
 
@@ -9842,15 +9843,15 @@
      * The base class implementation makes the thumbnail the same size and appearance
      * as the view itself, and positions it with its center at the touch point.
      */
-    public class DragThumbnailBuilder {
-        private View mView;
+    public static class DragThumbnailBuilder {
+        private final WeakReference<View> mView;
 
         /**
          * Construct a thumbnail builder object for use with the given view.
          * @param view
          */
         public DragThumbnailBuilder(View view) {
-            mView = view;
+            mView = new WeakReference<View>(view);
         }
 
         /**
@@ -9870,8 +9871,13 @@
          *        the touch point on the screen during a drag.
          */
         public void onProvideThumbnailMetrics(Point thumbnailSize, Point thumbnailTouchPoint) {
-            thumbnailSize.set(mView.getWidth(), mView.getHeight());
-            thumbnailTouchPoint.set(thumbnailSize.x / 2, thumbnailSize.y / 2);
+            final View view = mView.get();
+            if (view != null) {
+                thumbnailSize.set(view.getWidth(), view.getHeight());
+                thumbnailTouchPoint.set(thumbnailSize.x / 2, thumbnailSize.y / 2);
+            } else {
+                Log.e(View.VIEW_LOG_TAG, "Asked for drag thumb metrics but no view");
+            }
         }
 
         /**
@@ -9882,7 +9888,12 @@
          * @param canvas
          */
         public void onDrawThumbnail(Canvas canvas) {
-            mView.draw(canvas);
+            final View view = mView.get();
+            if (view != null) {
+                view.draw(canvas);
+            } else {
+                Log.e(View.VIEW_LOG_TAG, "Asked to draw drag thumb but no view");
+            }
         }
     }
 
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 727cf17..11e68c5 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -27,7 +27,6 @@
 import android.os.Environment;
 import android.os.Debug;
 import android.os.RemoteException;
-import dalvik.system.VMDebug;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -431,7 +430,7 @@
      * @hide
      */
     public static long getViewInstanceCount() {
-        return VMDebug.countInstancesOfClass(View.class);
+        return Debug.countInstancesOfClass(View.class);
     }
 
     /**
@@ -442,7 +441,7 @@
      * @hide
      */
     public static long getViewRootInstanceCount() {
-        return VMDebug.countInstancesOfClass(ViewRoot.class);
+        return Debug.countInstancesOfClass(ViewRoot.class);
     }
 
     /**
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index e71a4d6..5ebc981 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -19,8 +19,6 @@
 import android.animation.LayoutTransition;
 import com.android.internal.R;
 
-import android.content.ClipData;
-import android.content.ClipDescription;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
@@ -963,7 +961,7 @@
         final View[] children = mChildren;
         for (int i = count - 1; i >= 0; i--) {
             final View child = children[i];
-            if (child.mCanAcceptDrop == false) {
+            if (!child.mCanAcceptDrop) {
                 continue;
             }
 
@@ -1179,16 +1177,20 @@
         return handled;
     }
 
-    /* Resets all touch state in preparation for a new cycle. */
-    private final void resetTouchState() {
+    /**
+     * Resets all touch state in preparation for a new cycle.
+     */
+    private void resetTouchState() {
         clearTouchTargets();
         resetCancelNextUpFlag(this);
         mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
     }
 
-    /* Resets the cancel next up flag.
-     * Returns true if the flag was previously set. */
-    private final boolean resetCancelNextUpFlag(View view) {
+    /**
+     * Resets the cancel next up flag.
+     * Returns true if the flag was previously set.
+     */
+    private boolean resetCancelNextUpFlag(View view) {
         if ((view.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
             view.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
             return true;
@@ -1196,8 +1198,10 @@
         return false;
     }
 
-    /* Clears all touch targets. */
-    private final void clearTouchTargets() {
+    /**
+     * Clears all touch targets.
+     */
+    private void clearTouchTargets() {
         TouchTarget target = mFirstTouchTarget;
         if (target != null) {
             do {
@@ -1209,8 +1213,10 @@
         }
     }
 
-    /* Cancels and clears all touch targets. */
-    private final void cancelAndClearTouchTargets(MotionEvent event) {
+    /**
+     * Cancels and clears all touch targets.
+     */
+    private void cancelAndClearTouchTargets(MotionEvent event) {
         if (mFirstTouchTarget != null) {
             boolean syntheticEvent = false;
             if (event == null) {
@@ -1232,9 +1238,11 @@
         }
     }
 
-    /* Gets the touch target for specified child view.
-     * Returns null if not found. */
-    private final TouchTarget getTouchTarget(View child) {
+    /**
+     * Gets the touch target for specified child view.
+     * Returns null if not found.
+     */
+    private TouchTarget getTouchTarget(View child) {
         for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
             if (target.child == child) {
                 return target;
@@ -1243,17 +1251,21 @@
         return null;
     }
 
-    /* Adds a touch target for specified child to the beginning of the list.
-     * Assumes the target child is not already present. */
-    private final TouchTarget addTouchTarget(View child, int pointerIdBits) {
+    /**
+     * Adds a touch target for specified child to the beginning of the list.
+     * Assumes the target child is not already present.
+     */
+    private TouchTarget addTouchTarget(View child, int pointerIdBits) {
         TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
         target.next = mFirstTouchTarget;
         mFirstTouchTarget = target;
         return target;
     }
 
-    /* Removes the pointer ids from consideration. */
-    private final void removePointersFromTouchTargets(int pointerIdBits) {
+    /**
+     * Removes the pointer ids from consideration.
+     */
+    private void removePointersFromTouchTargets(int pointerIdBits) {
         TouchTarget predecessor = null;
         TouchTarget target = mFirstTouchTarget;
         while (target != null) {
@@ -1276,10 +1288,12 @@
         }
     }
 
-    /* Returns true if a child view contains the specified point when transformed
+    /**
+     * Returns true if a child view contains the specified point when transformed
      * into its coordinate space.
-     * Child must not be null. */
-    private final boolean isTransformedTouchPointInView(float x, float y, View child,
+     * Child must not be null.
+     */
+    private boolean isTransformedTouchPointInView(float x, float y, View child,
             PointF outLocalPoint) {
         float localX = x + mScrollX - child.mLeft;
         float localY = y + mScrollY - child.mTop;
@@ -1298,10 +1312,12 @@
         return isInView;
     }
 
-    /* Transforms a motion event into the coordinate space of a particular child view,
+    /**
+     * Transforms a motion event into the coordinate space of a particular child view,
      * filters out irrelevant pointer ids, and overrides its action if necessary.
-     * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. */
-    private final boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
+     * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
+     */
+    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
             View child, int desiredPointerIdBits) {
         final boolean handled;
 
@@ -1476,9 +1492,11 @@
         return handled;
     }
 
-    /* Enlarge the temporary pointer arrays for splitting pointers.
-     * May discard contents (but keeps PointerCoords objects to avoid reallocating them). */
-    private final void growTmpPointerArrays(int desiredCapacity) {
+    /**
+     * Enlarge the temporary pointer arrays for splitting pointers.
+     * May discard contents (but keeps PointerCoords objects to avoid reallocating them).
+     */
+    private void growTmpPointerArrays(int desiredCapacity) {
         final MotionEvent.PointerCoords[] oldTmpPointerCoords = mTmpPointerCoords;
         int capacity;
         if (oldTmpPointerCoords != null) {
@@ -2174,7 +2192,8 @@
             if (!canvas.isHardwareAccelerated()) {
                 cache = child.getDrawingCache(true);
             } else {
-                displayList = child.getDisplayList();
+                // TODO: bring back
+                // displayList = child.getDisplayList();
             }
         }
 
diff --git a/core/java/android/webkit/DeviceOrientationService.java b/core/java/android/webkit/DeviceOrientationService.java
index 4ff849e..9b866d3 100755
--- a/core/java/android/webkit/DeviceOrientationService.java
+++ b/core/java/android/webkit/DeviceOrientationService.java
@@ -41,6 +41,7 @@
     private Double mAlpha;
     private Double mBeta;
     private Double mGamma;
+    private boolean mHaveSentErrorEvent;
 
     private static final double DELTA_DEGRESS = 1.0;
 
@@ -75,11 +76,16 @@
 
     private void sendErrorEvent() {
         assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
+        // The spec requires that each listener receives the error event only once.
+        if (mHaveSentErrorEvent)
+            return;
+        mHaveSentErrorEvent = true;
         mHandler.post(new Runnable() {
             @Override
             public void run() {
                 assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
                 if (mIsRunning) {
+                    // The special case of all nulls is used to signify a failure to get data.
                     mManager.onOrientationChange(null, null, null);
                 }
             }
@@ -169,6 +175,8 @@
             mBeta = beta;
             mGamma = gamma;
             mManager.onOrientationChange(mAlpha, mBeta, mGamma);
+            // Now that we have successfully sent some data, reset whether we've sent an error.
+            mHaveSentErrorEvent = false;
         }
     }
 
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 7462668..7c089d8 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -22,6 +22,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.media.MediaFile;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Looper;
@@ -252,6 +253,13 @@
         return mSettings;
     }
 
+    /*
+     * Given mimeType, check whether it's supported in Android media framework.
+     * mimeType could be such as "audio/ogg" and "video/mp4".
+     */
+    /* package */ static boolean supportsMimeType(String mimeType) {
+        return MediaFile.getFileTypeForMimeType(mimeType) > 0;
+    }
     /**
      * Add an error message to the client's console.
      * @param message The message to add
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index 1d1e601..b7b1a23 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -324,7 +324,11 @@
     }
 
     private int modulo(int pos, int size) {
-        return (size + (pos % size)) % size;
+        if (size > 0) {
+            return (size + (pos % size)) % size;
+        } else {
+            return 0;
+        }
     }
 
     /**
@@ -383,6 +387,8 @@
 
     void showOnly(int childIndex, boolean animate, boolean onLayout) {
         if (mAdapter == null) return;
+        final int adapterCount = mAdapter.getCount();
+        if (adapterCount == 0) return;
 
         for (int i = 0; i < mPreviousViews.size(); i++) {
             View viewToRemove = mViewsMap.get(mPreviousViews.get(i)).view;
@@ -399,7 +405,6 @@
             removeViewInLayout(viewToRemove);
         }
         mPreviousViews.clear();
-        int adapterCount = mAdapter.getCount();
         int newWindowStartUnbounded = childIndex - mActiveOffset;
         int newWindowEndUnbounded = newWindowStartUnbounded + mNumActiveViews - 1;
         int newWindowStart = Math.max(0, newWindowStartUnbounded);
diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java
index 839de7d..f0954e2 100644
--- a/core/java/android/widget/StackView.java
+++ b/core/java/android/widget/StackView.java
@@ -749,7 +749,9 @@
         if (mAdapter != null && mWhichChild == -1) {
             mWhichChild = mAdapter.getCount() - 1;
         }
-        setDisplayedChild(mWhichChild);
+        if (mWhichChild >= 0) {
+            setDisplayedChild(mWhichChild);
+        }
     }
 
     LayoutParams createOrReuseLayoutParams(View v) {
diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java
index 4a0617c..fefdcea 100644
--- a/core/java/com/android/internal/app/AlertController.java
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -17,6 +17,9 @@
 package com.android.internal.app;
 
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import com.android.internal.R;
+
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -32,10 +35,11 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
 import android.view.Window;
 import android.view.WindowManager;
-import android.view.ViewGroup.LayoutParams;
 import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
 import android.widget.ArrayAdapter;
 import android.widget.Button;
 import android.widget.CheckedTextView;
@@ -48,9 +52,6 @@
 import android.widget.ScrollView;
 import android.widget.SimpleCursorAdapter;
 import android.widget.TextView;
-import android.widget.AdapterView.OnItemClickListener;
-
-import com.android.internal.R;
 
 import java.lang.ref.WeakReference;
 
@@ -403,12 +404,10 @@
             
             mIconView = (ImageView) mWindow.findViewById(R.id.icon);
             if (hasTextTitle) {
-                
                 /* Display the title if a title is supplied, else hide it */
                 mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle);
 
                 mTitleView.setText(mTitle);
-                mIconView.setImageResource(R.drawable.ic_dialog_menu_generic);
                 
                 /* Do this last so that if the user has supplied any
                  * icons we use them instead of the default ones. If the
@@ -689,7 +688,7 @@
         public final Context mContext;
         public final LayoutInflater mInflater;
         
-        public int mIconId = -1;
+        public int mIconId = 0;
         public Drawable mIcon;
         public CharSequence mTitle;
         public View mCustomTitleView;
diff --git a/core/res/res/anim/fragment_close_enter.xml b/core/res/res/anim/fragment_close_enter.xml
index 53afa2a..7a9a3b9 100644
--- a/core/res/res/anim/fragment_close_enter.xml
+++ b/core/res/res/anim/fragment_close_enter.xml
@@ -19,20 +19,6 @@
 <set xmlns:android="http://schemas.android.com/apk/res/android">
     <objectAnimator
         android:interpolator="@anim/decelerate_interpolator"
-        android:valueFrom="2"
-        android:valueTo="1"
-        android:valueType="floatType"
-        android:propertyName="scaleX"
-        android:duration="@android:integer/config_mediumAnimTime"/>
-    <objectAnimator
-        android:interpolator="@anim/decelerate_interpolator"
-        android:valueFrom="2"
-        android:valueTo="1"
-        android:valueType="floatType"
-        android:propertyName="scaleY"
-        android:duration="@android:integer/config_mediumAnimTime"/>
-    <objectAnimator
-        android:interpolator="@anim/decelerate_interpolator"
         android:valueFrom="0"
         android:valueTo="1"
         android:valueType="floatType"
diff --git a/core/res/res/anim/fragment_close_exit.xml b/core/res/res/anim/fragment_close_exit.xml
index 1554a4e..0743577 100644
--- a/core/res/res/anim/fragment_close_exit.xml
+++ b/core/res/res/anim/fragment_close_exit.xml
@@ -20,20 +20,6 @@
     <objectAnimator
         android:interpolator="@anim/accelerate_interpolator"
         android:valueFrom="1"
-        android:valueTo=".5"
-        android:valueType="floatType"
-        android:propertyName="scaleX"
-        android:duration="@android:integer/config_mediumAnimTime"/>
-    <objectAnimator
-        android:interpolator="@anim/accelerate_interpolator"
-        android:valueFrom="1"
-        android:valueTo=".5"
-        android:valueType="floatType"
-        android:propertyName="scaleY"
-        android:duration="@android:integer/config_mediumAnimTime"/>
-    <objectAnimator
-        android:interpolator="@anim/accelerate_interpolator"
-        android:valueFrom="1"
         android:valueTo="0"
         android:valueType="floatType"
         android:propertyName="alpha"
diff --git a/core/res/res/anim/fragment_next_enter.xml b/core/res/res/anim/fragment_next_enter.xml
new file mode 100644
index 0000000..d2d6ec9
--- /dev/null
+++ b/core/res/res/anim/fragment_next_enter.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <objectAnimator
+        android:valueFrom="0"
+        android:valueTo="1"
+        android:valueType="floatType"
+        android:propertyName="alpha"
+        android:duration="@android:integer/config_longAnimTime"/>
+    <objectAnimator
+        android:valueFrom="50"
+        android:valueTo="0"
+        android:valueType="floatType"
+        android:propertyName="translationY"
+        android:duration="@android:integer/config_longAnimTime"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/fragment_next_exit.xml b/core/res/res/anim/fragment_next_exit.xml
new file mode 100644
index 0000000..fbb82d9
--- /dev/null
+++ b/core/res/res/anim/fragment_next_exit.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <objectAnimator
+        android:valueFrom="1"
+        android:valueTo="0"
+        android:valueType="floatType"
+        android:propertyName="alpha"
+        android:duration="@android:integer/config_mediumAnimTime"/>
+    <objectAnimator
+        android:valueFrom="0"
+        android:valueTo="-50"
+        android:valueType="floatType"
+        android:propertyName="translationY"
+        android:duration="@android:integer/config_mediumAnimTime"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/fragment_open_enter.xml b/core/res/res/anim/fragment_open_enter.xml
index 142f60c..ac60494 100644
--- a/core/res/res/anim/fragment_open_enter.xml
+++ b/core/res/res/anim/fragment_open_enter.xml
@@ -18,20 +18,6 @@
 -->
 <set xmlns:android="http://schemas.android.com/apk/res/android">
     <objectAnimator
-        android:interpolator="@anim/decelerate_interpolator"
-        android:valueFrom="2"
-        android:valueTo="1"
-        android:valueType="floatType"
-        android:propertyName="scaleX"
-        android:duration="@android:integer/config_mediumAnimTime"/>
-    <objectAnimator
-        android:interpolator="@anim/decelerate_interpolator"
-        android:valueFrom="2"
-        android:valueTo="1"
-        android:valueType="floatType"
-        android:propertyName="scaleY"
-        android:duration="@android:integer/config_mediumAnimTime"/>
-    <objectAnimator
         android:valueFrom="0"
         android:valueTo="1"
         android:valueType="floatType"
diff --git a/core/res/res/anim/fragment_open_exit.xml b/core/res/res/anim/fragment_open_exit.xml
index 21260b9..3bf1ad4 100644
--- a/core/res/res/anim/fragment_open_exit.xml
+++ b/core/res/res/anim/fragment_open_exit.xml
@@ -18,20 +18,6 @@
 -->
 <set xmlns:android="http://schemas.android.com/apk/res/android">
     <objectAnimator
-        android:interpolator="@anim/accelerate_interpolator"
-        android:valueFrom="1"
-        android:valueTo="2"
-        android:valueType="floatType"
-        android:propertyName="scaleX"
-        android:duration="@android:integer/config_mediumAnimTime"/>
-    <objectAnimator
-        android:interpolator="@anim/accelerate_interpolator"
-        android:valueFrom="1"
-        android:valueTo="2"
-        android:valueType="floatType"
-        android:propertyName="scaleY"
-        android:duration="@android:integer/config_mediumAnimTime"/>
-    <objectAnimator
         android:valueFrom="1"
         android:valueTo="0"
         android:valueType="floatType"
diff --git a/core/res/res/anim/fragment_prev_enter.xml b/core/res/res/anim/fragment_prev_enter.xml
new file mode 100644
index 0000000..d37afd0
--- /dev/null
+++ b/core/res/res/anim/fragment_prev_enter.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <objectAnimator
+        android:valueFrom="0"
+        android:valueTo="1"
+        android:valueType="floatType"
+        android:propertyName="alpha"
+        android:duration="@android:integer/config_longAnimTime"/>
+    <objectAnimator
+        android:valueFrom="-50"
+        android:valueTo="0"
+        android:valueType="floatType"
+        android:propertyName="translationY"
+        android:duration="@android:integer/config_longAnimTime"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/fragment_prev_exit.xml b/core/res/res/anim/fragment_prev_exit.xml
new file mode 100644
index 0000000..a445a4d
--- /dev/null
+++ b/core/res/res/anim/fragment_prev_exit.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <objectAnimator
+        android:valueFrom="1"
+        android:valueTo="0"
+        android:valueType="floatType"
+        android:propertyName="alpha"
+        android:duration="@android:integer/config_mediumAnimTime"/>
+    <objectAnimator
+        android:valueFrom="0"
+        android:valueTo="50"
+        android:valueType="floatType"
+        android:propertyName="translationY"
+        android:duration="@android:integer/config_mediumAnimTime"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/drawable-hdpi/ic_dialog_menu_generic.png b/core/res/res/drawable-hdpi/ic_dialog_menu_generic.png
deleted file mode 100644
index ef8a877..0000000
--- a/core/res/res/drawable-hdpi/ic_dialog_menu_generic.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/status_bar_close_on.9.png b/core/res/res/drawable-hdpi/status_bar_close_on.9.png
deleted file mode 100644
index f313ffb..0000000
--- a/core/res/res/drawable-hdpi/status_bar_close_on.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_dialog_menu_generic.png b/core/res/res/drawable-mdpi/ic_dialog_menu_generic.png
deleted file mode 100755
index de07bda..0000000
--- a/core/res/res/drawable-mdpi/ic_dialog_menu_generic.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/status_bar_close_on.9.png b/core/res/res/drawable-mdpi/status_bar_close_on.9.png
deleted file mode 100644
index 9cbd9fe..0000000
--- a/core/res/res/drawable-mdpi/status_bar_close_on.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 8e2b762..33d3eeb 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1088,6 +1088,10 @@
         <attr name="fragmentOpenExitAnimation" format="reference" />
         <attr name="fragmentCloseEnterAnimation" format="reference" />
         <attr name="fragmentCloseExitAnimation" format="reference" />
+        <attr name="fragmentNextEnterAnimation" format="reference" />
+        <attr name="fragmentNextExitAnimation" format="reference" />
+        <attr name="fragmentPrevEnterAnimation" format="reference" />
+        <attr name="fragmentPrevExitAnimation" format="reference" />
     </declare-styleable>
 
     <!-- Window animation class attributes. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 35f8df5..2c3c4fc 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1332,6 +1332,10 @@
   <public type="attr" name="fragmentOpenExitAnimation" />
   <public type="attr" name="fragmentCloseEnterAnimation" />
   <public type="attr" name="fragmentCloseExitAnimation" />
+  <public type="attr" name="fragmentNextEnterAnimation" />
+  <public type="attr" name="fragmentNextExitAnimation" />
+  <public type="attr" name="fragmentPrevEnterAnimation" />
+  <public type="attr" name="fragmentPrevExitAnimation" />
   <public type="attr" name="actionBarSize" />
   <public type="attr" name="imeSubtypeLocale" />
   <public type="attr" name="imeSubtypeMode" />
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 3dfaf7f..4b5047e 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -78,6 +78,10 @@
         <item name="fragmentOpenExitAnimation">@anim/fragment_open_exit</item>
         <item name="fragmentCloseEnterAnimation">@anim/fragment_close_enter</item>
         <item name="fragmentCloseExitAnimation">@anim/fragment_close_exit</item>
+        <item name="fragmentNextEnterAnimation">@anim/fragment_next_enter</item>
+        <item name="fragmentNextExitAnimation">@anim/fragment_next_exit</item>
+        <item name="fragmentPrevEnterAnimation">@anim/fragment_prev_enter</item>
+        <item name="fragmentPrevExitAnimation">@anim/fragment_prev_exit</item>
     </style>
 
     <!-- Standard animations for a non-full-screen window or activity. -->
diff --git a/core/tests/coretests/src/android/content/SyncQueueTest.java b/core/tests/coretests/src/android/content/SyncQueueTest.java
deleted file mode 100644
index 1da59d1..0000000
--- a/core/tests/coretests/src/android/content/SyncQueueTest.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.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).first);
-        mSyncQueue.remove(op6);
-
-        assertEquals(op1, mSyncQueue.nextReadyToRun(now).first);
-        mSyncQueue.remove(op1);
-
-        assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
-        mSyncQueue.remove(op4);
-
-        assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
-        mSyncQueue.remove(op5);
-
-        assertEquals(op2, mSyncQueue.nextReadyToRun(now).first);
-        mSyncQueue.remove(op2);
-
-        assertEquals(op3, mSyncQueue.nextReadyToRun(now).first);
-        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).first);
-        mSyncQueue.remove(op6);
-
-        assertEquals(op1, mSyncQueue.nextReadyToRun(now).first);
-        mSyncQueue.remove(op1);
-
-        mSettings.setBackoff(ACCOUNT2,  AUTHORITY3, now + 200, 5);
-        assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
-
-        mSettings.setBackoff(ACCOUNT2,  AUTHORITY3, SyncStorageEngine.NOT_IN_BACKOFF_MODE, 0);
-        assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
-
-        mSettings.setDelayUntilTime(ACCOUNT2,  AUTHORITY3, now + 200);
-        assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
-
-        mSettings.setDelayUntilTime(ACCOUNT2,  AUTHORITY3, 0);
-        assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
-        mSyncQueue.remove(op4);
-
-        assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
-        mSyncQueue.remove(op5);
-
-        assertEquals(op2, mSyncQueue.nextReadyToRun(now).first);
-        mSyncQueue.remove(op2);
-
-        assertEquals(op3, mSyncQueue.nextReadyToRun(now).first);
-        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;
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
index bbffe70..f6b1d04 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
@@ -97,7 +98,10 @@
         assertTrue(db.isOpen());
     }
 
-    @SmallTest
+    /**
+     * this test could take a while to execute. so, designate it as LargetTest
+     */
+    @LargeTest
     public void testFillWindow() {
         // create schema
         final String testTable = "testV";
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java
index 217545f..955336a 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java
@@ -128,7 +128,7 @@
      * pre-compiled SQL statement id except in during the period of binding the arguments
      * and executing the SQL statement.
      */
-    @SmallTest
+    @LargeTest
     public void testReferenceToPrecompiledStatementId() {
         mDatabase.execSQL("create table t (i int, j text);");
         verifyReferenceToPrecompiledStatementId(false);
diff --git a/docs/html/guide/practices/design/responsiveness.jd b/docs/html/guide/practices/design/responsiveness.jd
index 2c7633d..b811d1b 100644
--- a/docs/html/guide/practices/design/responsiveness.jd
+++ b/docs/html/guide/practices/design/responsiveness.jd
@@ -19,19 +19,6 @@
 <p><strong>Figure 1.</strong> An ANR dialog displayed to the user.</p>
 </div>
 
-<p>It's possible to write code that wins every performance test in the world, but still sends users
-in a fiery rage when they try to use the application. These are the applications that aren't
-<em>responsive</em> enough &mdash; the ones that feel
-sluggish, hang or freeze for significant periods, or take too long to process
-input. </p>
-
-<p>In Android, the system guards against applications that are insufficiently responsive for a
-period of time by displaying a dialog to the user, called the Application Not Responding (ANR)
-dialog. The user can choose to let the application continue, but the user won't appreciate having to
-act on this dialog every time he or she uses your application. So it's important to design
-responsiveness into your application, so that the system never has cause to display an ANR to the
-user. </p>
-
 <p>It's possible to write code that wins every performance test in the world,
 but still sends users in a fiery rage when they try to use the application.
 These are the applications that aren't <em>responsive</em> enough &mdash; the
diff --git a/docs/html/guide/topics/fundamentals/activities.jd b/docs/html/guide/topics/fundamentals/activities.jd
new file mode 100644
index 0000000..b616ab8
--- /dev/null
+++ b/docs/html/guide/topics/fundamentals/activities.jd
@@ -0,0 +1,762 @@
+page.title=Activities
+parent.title=Application Fundamentals
+parent.link=index.html
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+<h2>Quickview</h2>
+<ul>
+  <li>An activity provides a user interface for a single screen in your application</li>
+  <li>Activities can move into the background and then be resumed with their state restored</li>
+</ul>
+
+<h2>In this document</h2>
+<ol>
+  <li><a href="#Creating">Creating an Activity</a>
+    <ol>
+      <li><a href="#UI">Implementing a user interface</a></li>
+      <li><a href="#Declaring">Declaring the activity in the manifest</a></li>
+    </ol>
+  </li>
+  <li><a href="#StartingAnActivity">Starting an Activity</a>
+    <ol>
+      <li><a href="#StartingAnActivityForResult">Starting an Activity for a Result</a></li>
+    </ol>
+  </li>
+  <li><a href="#Lifecycle">Managing the Activity Lifecycle</a>
+    <ol>
+      <li><a href="#ImplementingLifecycleCallbacks">Implementing the lifecycle callbacks</a></li>
+      <li><a href="#SavingActivityState">Saving activity state</a></li>
+      <li><a href="#ConfigurationChanges">Handling configuration changes</a></li>
+      <li><a href="#CoordinatingActivities">Coordinating activities</a></li>
+    </ol>
+  </li>
+</ol>
+
+<h2>Key classes</h2>
+<ol>
+  <li>{@link android.app.Activity}</li>
+</ol>
+
+<h2>See also</h2>
+<ol>
+  <li><a href="{@docRoot}resources/tutorials/hello-world.html">Hello World Tutorial</a></li>
+  <li><a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack">Tasks and Back
+Stack</a></li>
+</ol>
+
+</div>
+</div>
+
+
+
+<p>An {@link android.app.Activity} is an application component that provides a screen with which
+users can interact in order to do something, such as dial the phone, take a photo, send an email, or
+view a map. Each activity is given a window in which to draw its user interface. The window
+typically fills the screen, but may be smaller than the screen and float on top of other
+windows.</p>
+
+<p> An application usually consists of multiple activities that are loosely bound
+to each other. Typically, one activity in an application is specified as the "main" activity, which
+is presented to the user when launching the application for the first time. Each
+activity can then start another activity in order to perform different actions. Each time a new
+activity starts, the previous activity is stopped, but the system preserves the activity
+in a stack (the "back stack"). When a new activity starts, it is pushed onto the back stack and
+takes user focus. The back stack abides to the basic "last in, first out" queue mechanism,
+so, when the user is done with the current activity and presses the BACK key, it
+is popped from the stack (and destroyed) and the previous activity resumes. (The back stack is
+discussed more in the <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks
+and Back Stack</a> document.)</p>
+
+<p>When an activity is stopped because a new activity starts, it is notified of this change in state
+through the activity's lifecycle callback methods.
+There are several callback methods that an activity might receive, due to a change in its
+state&mdash;whether the system is creating it, stopping it, resuming it, or destroying it&mdash;and
+each callback provides you the opportunity to perform specific work that's
+appropriate to that state change. For instance, when stopped, your activity should release any
+large objects, such as network or database connections. When the activity resumes, you can
+reacquire the necessary resources and resume actions that were interrupted. These state transitions
+are all part of the activity lifecycle.</p>
+
+<p>The rest of this document discusses the basics of how to build and use an activity,
+including a complete discussion of how the activity lifecycle works, so you can properly manage
+the transition between various activity states.</p>
+
+
+
+<h2 id="Creating">Creating an Activity</h2>
+
+<p>To create an activity, you must create a subclass of {@link android.app.Activity} (or
+an existing subclass of it). In your subclass, you need to implement callback methods that the
+system calls when the activity transitions between various states of its lifecycle, such as when
+the activity is being created, stopped, resumed, or destroyed. The two most important callback
+methods are:</p>
+
+<dl>
+  <dt>{@link android.app.Activity#onCreate onCreate()}</dt>
+  <dd>You must implement this method. The system calls this when creating your
+    activity. Within your implementation, you should initialize the essential components of your
+activity.
+    Most importantly, this is where you must call {@link android.app.Activity#setContentView
+    setContentView()} to define the layout for the activity's user interface.</dd>
+  <dt>{@link android.app.Activity#onPause onPause()}</dt>
+  <dd>The system calls this method as the first indication that the user is leaving your
+activity (though it does not always mean the activity is being destroyed). This is usually where you
+should commit any changes that should be persisted beyond the current user session (because
+the user might not come back).</dd>
+</dl>
+
+<p>There are several other lifecycle callback methods that you should use in order to provide a
+fluid user experience between activities and handle unexpected interuptions that cause your activity
+to be stopped and even destroyed. All of the lifecycle callback methods are discussed later, in
+the section about <a href="#Lifecycle">Managing the Activity Lifecycle</a>.</p>
+
+
+
+<h3 id="UI">Implementing a user interface</h3>
+
+<p> The user interface for an activity is provided by a hierarchy of views&mdash;objects derived
+from the {@link android.view.View} class.  Each view controls a particular rectangular space
+within the activity's window and can respond to user interaction. For example, a view might be a
+button that initiates an action when the user touches it.</p>
+
+<p>Android provides a number of ready-made views that you can use to design and organize your
+layout. "Widgets" are views that provide a visual (and interactive) elements for the screen, such
+as a button, text field, checkbox, or just an image. "Layouts" are views derived from {@link
+android.view.ViewGroup} that provide a unique layout model for its child views, such as a linear
+layout, a grid layout, or relative layout. You can also subclass the {@link android.view.View} and
+{@link android.view.ViewGroup} classes (or existing subclasses) to create your own widgets and
+layouts and apply them to your activity layout.</p>
+
+<p>The most common way to define a layout using views is with an XML layout file saved in your
+application resources. This way, you can maintain the design of your user interface separately from
+the source code that defines the activity's behavior. You can set the layout as the UI for your
+activity with {@link android.app.Activity#setContentView(int) setContentView()}, passing the
+resource ID for the layout. However, you can also create new {@link android.view.View}s in your
+activity code and build a view hierarchy by inserting new {@link
+android.view.View}s into a {@link android.view.ViewGroup}, then use that layout by passing the root
+{@link android.view.ViewGroup} to {@link android.app.Activity#setContentView(View)
+setContentView()}.</p>
+
+<p>For information about creating a user interface, see the <a
+href="{@docRoot}guide/topics/ui/index.html">User Interface</a> documentation.</p>
+
+
+
+<h3 id="Declaring">Declaring the activity in the manifest</h3>
+
+<p>You must declare your activity in the manifest file in order for it to
+be accessible to the system. To decalare your activity, open your manifest file and add an <a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code &lt;activity&gt;}</a> element
+as a child of the <a
+href="{@docRoot}guide/topics/manifest/application-element.html">{@code &lt;application&gt;}</a>
+element. For example:</p>
+
+<pre>
+&lt;manifest ... &gt;
+  &lt;application ... &gt;
+      &lt;activity android:name=".ExampleActivity" /&gt;
+      ...
+  &lt;/application ... &gt;
+  ...
+&lt;/manifest &gt;
+</pre>
+
+<p>There are several other attributes that you can include in this element, to define properties
+such as the label for the activity, an icon for the activity, or a theme to style the activity's
+UI. See the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code &lt;activity&gt;}</a> element
+reference for more information about available attributes.</p>
+
+
+<h4>Using intent filters</h4>
+
+<p>An <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code
+&lt;activity&gt;}</a> element can also specify various intent filters&mdash;using the <a
+href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code
+&lt;intent-filter&gt;}</a> element&mdash;in order to declare how other application components may
+activate it.</p>
+
+<p>When you create a new application using the Android SDK tools, the stub activity
+that's created for you automatically includes an intent filter that declares the activity
+responds to the "main" action and should be placed in the "launcher" category. The intent filter
+looks like this:</p>
+
+<pre>
+&lt;activity android:name=".ExampleActivity" android:icon="@drawable/app_icon"&gt;
+    &lt;intent-filter&gt;
+        &lt;action android:name="android.intent.action.MAIN" /&gt;
+        &lt;category android:name="android.intent.category.LAUNCHER" /&gt;
+    &lt;/intent-filter&gt;
+&lt;/activity&gt;
+</pre>
+
+<p>The <a href="{@docRoot}guide/topics/manifest/action-element.html">{@code
+&lt;action&gt;}</a> element specifies that this is the "main" entry point to the application. The <a
+href="{@docRoot}guide/topics/manifest/category-element.html">{@code
+&lt;category&gt;}</a> element specifies that this activity should be listed in the
+system's application launcher (to allow users to launch this activity).</p>
+
+<p>If you intend for your application to be self-contained and not allow other applications to
+activate its activities, then you don't need any other intent filters. Only one activity should
+have the "main" action and "launcher" category, as in the previous example. Activities that
+you don't want to make available to other applications should have no intent filters and you can
+start them yourself using explicit intents (as discussed in the following section).</p>
+
+<p>However, if you want your activity to respond to implicit intents that are delivered from
+other applications (and your own), then you must define additional intent filters for your
+activity. For each type of intent to which you want to respond, you must include an <a
+href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code
+&lt;intent-filter&gt;}</a> that includes an
+<a href="{@docRoot}guide/topics/manifest/action-element.html">{@code
+&lt;action&gt;}</a> element and, optionally, a <a
+href="{@docRoot}guide/topics/manifest/category-element.html">{@code
+&lt;category&gt;}</a> element and/or a <a
+href="{@docRoot}guide/topics/manifest/data-element.html">{@code
+&lt;data&gt;}</a> element. These elements specify the type of intent to which your activity can
+respond.</p>
+
+<p>For more information about how your activities can respond to intents, see the <a
+href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent Filters</a>
+document.</p>
+
+
+
+<h2 id="StartingAnActivity">Starting an Activity</h2>
+
+<p>You can start another activity by calling {@link android.app.Activity#startActivity
+  startActivity()}, passing it an {@link android.content.Intent} that describes the activity you
+  want to start. The intent specifies either the exact activity you want to start or describes the
+  type of action you want to perform (and the system selects the appropriate activity for you,
+which
+  can even be from a different application). An intent can also carry small amounts of data to be
+  used by the activity that is started.</p>
+
+<p>When working within your own application, you'll often need to simply launch a known activity.
+ You can do so by creating an intent that explicitly defines the activity you want to start,
+using the class name. For example, here's how one activity starts another activity named {@code
+SignInActivity}:</p>
+
+<pre>
+Intent intent = new Intent(this, SignInActivity.class);
+startActivity(intent);
+</pre>
+
+<p>However, your application might also want to perform some action, such as send an email, text
+  message, or status update, using data from your activity. In this case, your application might
+ not have its own activities to perform such actions, so you can instead leverage the activities
+  provided by other applications on the device, which can perform the actions for you. This is where
+intents are really valuable&mdash;you can create an intent that describes an action you want to
+perform and the system
+  launches the appropriate activity from another application. If there are
+  multiple activities that can handle the intent, then the user can select which one to use. For
+  example, if you want to allow the user to send an email message, you can create the
+  following intent:</p>
+
+<pre>
+Intent intent = new Intent(Intent.ACTION_SEND);
+intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
+startActivity(intent);
+</pre>
+
+<p>The {@link android.content.Intent#EXTRA_EMAIL} extra added to the intent is a string array of
+  email addresses to which the email should be sent. When an email application responds to this
+  intent, it reads the string array provided in the extra and places them in the "to" field of the
+  email composition form. In this situation, the email application's activity starts and when the
+  user is done, your activity resumes.</p>
+
+
+
+
+<h3 id="StartingAnActivityForResult">Starting an activity for a result</h3>
+
+<p>Sometimes, you might want to receive a result from the activity that you start. In that case,
+  start the activity by calling {@link android.app.Activity#startActivityForResult
+  startActivityForResult()} (instead of {@link android.app.Activity#startActivity
+  startActivity()}). To then receive the result from the subsequent
+activity, implement the {@link android.app.Activity#onActivityResult onActivityResult()} callback
+  method. When the subsequent activity is done, it returns a result in an {@link
+android.content.Intent} to your {@link android.app.Activity#onActivityResult onActivityResult()}
+method.</p>
+
+<p>For example, perhaps you want the user to pick one of their contacts, so your activity can
+do something with the information in that contact. Here's how you can create such an intent and
+handle the result:</p>
+
+<pre>
+private void pickContact() {
+    // Create an intent to "pick" a contact, as defined by the content provider URI
+    Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
+    startActivityForResult(intent, PICK_CONTACT_REQUEST);
+}
+
+&#64;Override
+protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+    // If the request went well (OK) and the request was PICK_CONTACT_REQUEST
+    if (resultCode == Activity.RESULT_OK &amp;&amp; requestCode == PICK_CONTACT_REQUEST) {
+        // Perform a query to the contact's content provider for the contact's name
+        Cursor cursor = getContentResolver().query(data.getData(),
+        new String[] {Contacts.DISPLAY_NAME}, null, null, null);
+        if (cursor.moveToFirst()) { // True if the cursor is not empty
+            int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
+            String name = cursor.getString(columnIndex);
+            // Do something with the selected contact's name...
+        }
+    }
+}
+</pre>
+
+<p>This example shows the basic logic you should use in your {@link
+android.app.Activity#onActivityResult onActivityResult()} method in order to handle an
+activity result. The first condition checks whether the request was successful&mdash;if it was, then
+the {@code resultCode} will be {@link android.app.Activity#RESULT_OK}&mdash;and whether the request
+to which this result is responding is known&mdash;in this case, the {@code requestCode} matches the
+second parameter sent with {@link android.app.Activity#startActivityForResult
+startActivityForResult()}. From there, the code handles the activity result by querying the
+data returned in an {@link android.content.Intent} (the {@code data} parameter).</p>
+
+<p>What happens is, a {@link
+android.content.ContentResolver} performs a query against a content provider, which returns a
+{@link android.database.Cursor} that allows the queried data to be read. For more information, see
+the <a
+href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a> document.</p>
+
+<p>For more information about using intents, see the <a
+href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent
+Filters</a> document.</p>
+
+
+<h2 id="ShuttingDown">Shutting Down an Activity</h2>
+
+<p>You can shut down an activity by calling its {@link android.app.Activity#finish
+finish()} method. You can also shut down a separate activity that you previously started by calling
+{@link android.app.Activity#finishActivity finishActivity()}.</p>
+
+<p class="note"><strong>Note:</strong> In most cases, you should not explicitly finish an activity
+using these methods. As discussed in the following section about the activity lifecycle, the
+Android system manages the life of an activity for you, so you do not need to finish your own
+activities. Calling these methods could adversely affect the expected user
+experience and should only be used when you absolutely do not want the user to return to this
+instance of the activity.</p>
+
+
+<h2 id="Lifecycle">Managing the Activity Lifecycle</h2>
+
+<p>Managing the lifecycle of your activities by implementing callback methods is
+crucial to developing a strong
+and flexible application. The lifecycle of an activity is directly affected by its association with
+other activities, its task and back stack.</p>
+
+<p>An activity can exist in essentially three states:</p>
+
+<dl>
+  <dt><i>Resumed</i></dt>
+    <dd>The activity is in the foreground of the screen and has user focus. (This state is
+also sometimes referred to as "running".)</dd>
+
+  <dt><i>Paused</i></dt>
+    <dd>Another activity is in the foreground and has focus, but this one is still visible. That is,
+another activity is visible on top of this one and that activity is partially transparent or doesn't
+cover the entire screen. A paused activity is completely alive (the {@link android.app.Activity}
+object is retained in memory, it maintains all state and member information, and remains attached to
+the window manager), but can be killed by the system in extremely low memory situations.</dd>
+
+  <dt><i>Stopped</i></dt>
+    <dd>The activity is completely obscured by another activity (the activity is now in the
+"background"). A stopped activity is also still alive (the {@link android.app.Activity}
+object is retained in memory, it maintains all state and member information, but is <em>not</em>
+attached to the window manager). However, it is no longer visible to the user and it
+can be killed by the system when memory is needed elsewhere.</dd>
+</dl>
+
+<p>If an activity is paused or stopped, the system can drop it from memory either by asking it to
+finish (calling its {@link android.app.Activity#finish finish()} method), or simply killing its
+process.  When the activity is opened again (after being finished or killed), it must be created all
+over.</p>
+
+
+
+<h3 id="ImplementingLifecycleCallbacks">Implementing the lifecycle callbacks</h3>
+
+<p>When an activity transitions into and out of the different states described above, it is notified
+through various callback methods. All of the callback methods are hooks that you
+can override to do appropriate work when the state of your activity changes. The following skeleton
+activity includes each of the fundamental lifecycle methods:</p>
+
+
+<pre>
+public class ExampleActivity extends Activity {
+    &#64;Override
+    public void {@link android.app.Activity#onCreate onCreate}(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // The activity is being created.
+    }
+    &#64;Override
+    protected void {@link android.app.Activity#onStart onStart()} {
+        super.onStart();
+        // The activity is about to become visible.
+    }
+    &#64;Override
+    protected void {@link android.app.Activity#onResume onResume()} {
+        super.onResume();
+        // The activity has become visible (it is now "resumed").
+    }
+    &#64;Override
+    protected void {@link android.app.Activity#onPause onPause()} {
+        super.onPause();
+        // Another activity is taking focus (this activity is about to be "paused").
+    }
+    &#64;Override
+    protected void {@link android.app.Activity#onStop onStop()} {
+        super.onStop();
+        // The activity is no longer visible (it is now "stopped")
+    }
+    &#64;Override
+    protected void {@link android.app.Activity#onDestroy onDestroy()} {
+        super.onDestroy();
+        // The activity is about to be destroyed.
+    }
+}
+</pre>
+
+<p class="note"><strong>Note:</strong> Your implementation of these lifecycle methods must
+always call the superclass implementation before doing any work, as shown in the examples above.</p>
+
+<p>Taken together, these methods define the entire lifecycle of an activity. By implementing these
+methods, you can monitor three nested loops in the activity lifecycle: </p>
+
+<ul>
+<li>The <b>entire lifetime</b> of an activity happens between the call to {@link
+android.app.Activity#onCreate onCreate()} and the call to {@link
+android.app.Activity#onDestroy}. Your activity should perform setup of
+"global" state (such as defining layout) in {@link android.app.Activity#onCreate onCreate()}, and
+release all remaining resources in {@link android.app.Activity#onDestroy}. For example, if your
+activity has a thread running in the background to download data from the network, it might create
+that thread in {@link android.app.Activity#onCreate onCreate()} and then stop the thread in {@link
+android.app.Activity#onDestroy}.</li>
+
+<li><p>The <b>visible lifetime</b> of an activity happens between the call to {@link
+android.app.Activity#onStart onStart()} and the call to {@link
+android.app.Activity#onStop onStop()}. During this time, the user can see the activity
+on-screen and interact with it. For example, {@link android.app.Activity#onStop onStop()} is called
+when a new activity starts and this one is no longer visible. Between these two methods, you can
+maintain resources that are needed to show the activity to the user. For example, you can register a
+{@link android.content.BroadcastReceiver} in {@link
+android.app.Activity#onStart onStart()} to monitor changes that impact your UI, and unregister
+it in {@link android.app.Activity#onStop onStop()} when the user can no longer see what you are
+displaying. The system might call {@link android.app.Activity#onStart onStart()} and {@link
+android.app.Activity#onStop onStop()} multiple times during the entire lifetime of the activity, as
+the activity alternates between being visible and hidden to the user.</p></li>
+
+<li><p>The <b>foreground lifetime</b> of an activity happens between the call to {@link
+android.app.Activity#onResume onResume()} and the call to {@link android.app.Activity#onPause
+onPause()}. During this time, the activity is in front of all other activities on screen and has
+user input focus.  An activity can frequently transition in and out of the foreground&mdash;for
+example, {@link android.app.Activity#onPause onPause()} is called when the device goes to sleep or
+when a dialog appears. Because this state can transition often, the code in these two methods should
+be fairly lightweight in order to avoid slow transitions that make the user wait.</p></li>
+</ul>
+
+<p>Figure 1 illustrates these loops and the paths an activity might take between states.
+The rectangles represent the callback methods you can implement to perform operations when
+the activity transitions between states. <p>
+
+<img src="{@docRoot}images/activity_lifecycle.png" alt="" />
+<p class="img-caption"><strong>Figure 1.</strong> The activity lifecycle.</p>
+
+<p>The same lifecycle callback methods are listed in table 1, which describes each of the callback
+methods in more detail and locates each one within the
+activity's overall lifecycle, including whether the system can kill the activity after the
+callback method completes.</p>
+
+<p class="table-caption"><strong>Table 1.</strong> A summary of the activity lifecycle's
+callback methods.</p>
+
+<table border="2" width="85%" frame="hsides" rules="rows">
+<colgroup align="left" span="3"></colgroup>
+<colgroup align="left"></colgroup>
+<colgroup align="center"></colgroup>
+<colgroup align="center"></colgroup>
+
+<thead>
+<tr><th colspan="3">Method</th> <th>Description</th> <th>Killable after?</th> <th>Next</th></tr>
+</thead>
+
+<tbody>
+<tr>
+  <td colspan="3" align="left"><code>{@link android.app.Activity#onCreate onCreate()}</code></td>
+  <td>Called when the activity is first created.
+      This is where you should do all of your normal static set up &mdash;
+      create views, bind data to lists, and so on.  This method is passed
+      a Bundle object containing the activity's previous state, if that
+      state was captured (see <a href="#actstate">Saving Activity State</a>,
+      later).
+      <p>Always followed by {@code onStart()}.</p></td>
+  <td align="center">No</td>
+      <td align="center">{@code onStart()}</td>
+</tr>
+
+<tr>
+   <td rowspan="5" style="border-left: none; border-right: none;">&nbsp;&nbsp;&nbsp;&nbsp;</td>
+   <td colspan="2" align="left"><code>{@link android.app.Activity#onRestart
+onRestart()}</code></td>
+   <td>Called after the activity has been stopped, just prior to it being
+       started again.
+       <p>Always followed by {@code onStart()}</p></td>
+   <td align="center">No</td>
+   <td align="center">{@code onStart()}</td>
+</tr>
+
+<tr>
+   <td colspan="2" align="left"><code>{@link android.app.Activity#onStart onStart()}</code></td>
+   <td>Called just before the activity becomes visible to the user.
+       <p>Followed by {@code onResume()} if the activity comes
+       to the foreground, or {@code onStop()} if it becomes hidden.</p></td>
+    <td align="center">No</td>
+    <td align="center">{@code onResume()} <br/>or<br/> {@code onStop()}</td>
+</tr>
+
+<tr>
+   <td rowspan="2" style="border-left: none;">&nbsp;&nbsp;&nbsp;&nbsp;</td>
+   <td align="left"><code>{@link android.app.Activity#onResume onResume()}</code></td>
+   <td>Called just before the activity starts
+       interacting with the user.  At this point the activity is at
+       the top of the activity stack, with user input going to it.
+       <p>Always followed by {@code onPause()}.</p></td>
+   <td align="center">No</td>
+   <td align="center">{@code onPause()}</td>
+</tr>
+
+<tr>
+   <td align="left"><code>{@link android.app.Activity#onPause onPause()}</code></td>
+   <td>Called when the system is about to start resuming another
+       activity.  This method is typically used to commit unsaved changes to
+       persistent data, stop animations and other things that may be consuming
+       CPU, and so on.  It should do whatever it does very quickly, because
+       the next activity will not be resumed until it returns.
+       <p>Followed either by {@code onResume()} if the activity
+       returns back to the front, or by {@code onStop()} if it becomes
+       invisible to the user.</td>
+   <td align="center"><strong style="color:#800000">Yes</strong></td>
+   <td align="center">{@code onResume()} <br/>or<br/> {@code onStop()}</td>
+</tr>
+
+<tr>
+   <td colspan="2" align="left"><code>{@link android.app.Activity#onStop onStop()}</code></td>
+   <td>Called when the activity is no longer visible to the user.  This
+       may happen because it is being destroyed, or because another activity
+       (either an existing one or a new one) has been resumed and is covering it.
+       <p>Followed either by {@code onRestart()} if
+       the activity is coming back to interact with the user, or by
+       {@code onDestroy()} if this activity is going away.</p></td>
+   <td align="center"><strong style="color:#800000">Yes</strong></td>
+   <td align="center">{@code onRestart()} <br/>or<br/> {@code onDestroy()}</td>
+</tr>
+
+<tr>
+   <td colspan="3" align="left"><code>{@link android.app.Activity#onDestroy
+onDestroy()}</code></td>
+   <td>Called before the activity is destroyed.  This is the final call
+       that the activity will receive.  It could be called either because the
+       activity is finishing (someone called <code>{@link android.app.Activity#finish
+       finish()}</code> on it), or because the system is temporarily destroying this
+       instance of the activity to save space.  You can distinguish
+       between these two scenarios with the <code>{@link
+       android.app.Activity#isFinishing isFinishing()}</code> method.</td>
+   <td align="center"><strong style="color:#800000">Yes</strong></td>
+   <td align="center"><em>nothing</em></td>
+</tr>
+</tbody>
+</table>
+
+<p>The column labeled "Killable after?" indicates whether or not the system can
+kill the process hosting the activity at any time <em>after the method returns</em>, without
+executing another line of the activity's code.  Three methods are marked "yes": ({@link
+android.app.Activity#onPause
+onPause()}, {@link android.app.Activity#onStop onStop()}, and {@link android.app.Activity#onDestroy
+onDestroy()}). Because {@link android.app.Activity#onPause onPause()} is the first
+of the three, once the activity is created, {@link android.app.Activity#onPause onPause()} is the
+last method that's guaranteed to be called before the process <em>can</em> be killed&mdash;if
+the system must recover memory in an emergency, then {@link
+android.app.Activity#onStop onStop()} and {@link android.app.Activity#onDestroy onDestroy()} might
+not be called. Therefore, you should use {@link android.app.Activity#onPause onPause()} to write
+crucial persistent data (such as user edits) to storage. However, you should be selective about
+what information must be retained during {@link android.app.Activity#onPause onPause()}, because any
+blocking procedures in this method block the transition to the next activity and slow the user
+experience.</p>
+
+<p> Methods that are marked "No" in the <b>Killable</b> column protect the process hosting the
+activity from being killed from the moment they are called.  Thus, an activity is killable
+from the time {@link android.app.Activity#onPause onPause()} returns to the time
+{@link android.app.Activity#onResume onResume()} is called. It will not again be killable until
+{@link android.app.Activity#onPause onPause()} is again called and returns. </p>
+
+<p class="note"><strong>Note:</strong> An activity that's not technically "killable" by this
+definition in table 1 might still be killed by the system&mdash;but that would happen only in
+extreme circumstances when there is no other recourse. When an activity might be killed is
+discussed more in the <a
+href="{@docRoot}guide/topics/fundamentals/processes-and-threading.html">Processes and
+Threading</a> document.</p>
+
+
+<h3 id="SavingActivityState">Saving activity state</h3>
+
+<p>The introduction to <a href="Lifecycle">Managing the Activity Lifecycle</a> briefly mentions that
+when an activity is paused or stopped, the state of the activity is retained. This is true because
+the {@link android.app.Activity} object is still held in memory when it is paused or
+stopped&mdash;all information about its members and current state is still alive. Thus, any changes
+the user made within the activity are retained in memory, so that when the activity returns to the
+foreground (when it "resumes"), those changes are still there.</p>
+
+<div class="figure" style="width:615px">
+<img src="{@docRoot}images/fundamentals/restore_instance.png" alt="" />
+<p class="img-caption"><strong>Figure 2.</strong> The two ways in which an activity returns to user
+focus with its state intact: either the activity is stopped, then resumed and the activity state
+remains intact (left), or the activity is destroyed, then recreated and the activity must restore
+the previous activity state (right).</p>
+</div>
+
+<p>However, when the system destroys an activity in order to recover memory, the {@link
+android.app.Activity} object is destroyed, so the system cannot simply resume it with its state
+intact. Instead, the system must recreate the {@link android.app.Activity} object if the user
+navigates back to it. Yet, the user is unaware
+that the system destroyed the activity and recreated it and, thus, probably
+expects the activity to be exactly as it was. In this situation, you can ensure that
+important information about the activity state is preserved by implementing an additional
+callback method that allows you to save information about the state of your activity and then
+restore it when the the system recreates the activity.</p>
+
+<p>The callback method in which you can save information about the current state of your activity is
+{@link android.app.Activity#onSaveInstanceState onSaveInstanceState()}. The system calls this method
+before making the activity vulnerable to being destroyed and passes it
+a {@link android.os.Bundle} object. The {@link android.os.Bundle} is where you can store 
+state information about the activity as name-value pairs, using methods such as {@link
+android.os.Bundle#putString putString()}. Then, if the system kills your activity's
+process and the user navigates back to your activity, the system passes the {@link
+android.os.Bundle} to {@link android.app.Activity#onCreate onCreate()} so you can restore the
+activity state you saved during {@link android.app.Activity#onSaveInstanceState
+onSaveInstanceState()}. If there is no state information to restore, then the {@link
+android.os.Bundle} passed to {@link android.app.Activity#onCreate onCreate()} is null.</p>
+
+<p class="note"><strong>Note:</strong> There's no guarantee that {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()} will be called before your
+activity is destroyed, because there are cases in which it won't be necessary to save the state
+(such as when the user leaves your activity using the BACK key, because the user is explicitly
+closing the activity). If the method is called, it is always called before {@link
+android.app.Activity#onStop onStop()} and possibly before {@link android.app.Activity#onPause
+onPause()}.</p>
+
+<p>However, even if you do nothing and do not implement {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()}, some of the activity state is
+restored by the {@link android.app.Activity} class's default implementation of {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()}. Specifically, the default
+implementation calls {@link
+android.view.View#onSaveInstanceState onSaveInstanceState()} for every {@link android.view.View}
+in the layout, which allows each view to provide information about itself
+that should be saved. Almost every widget in the Android framework implements this method as
+appropriate, such that any visible changes to the UI are automatically saved and restored when your
+activity is recreated. For example, the {@link android.widget.EditText} widget saves any text
+entered by the user and the {@link android.widget.CheckBox} widget saves whether it's checked or
+not. The only work required by you is to provide a unique ID (with the <a
+href="{@docRoot}guide/topics/resources/layout-resource.html#idvalue">{@code android:id}</a>
+attribute) for each widget you want to save its state. If a widget does not have an ID, then it
+cannot save its state.</p>
+
+<div class="sidebox-wrapper">
+<div class="sidebox">
+<p>You can also explicitly stop a view in your layout from saving its state by setting the
+{@link android.R.attr#saveEnabled android:saveEnabled} attribute to {@code "false"} or by calling
+the {@link android.view.View#setSaveEnabled setSaveEnabled()} method. Usually, you should not
+disable this, but you might if you want to restore the state of the activity UI differently.</p>
+</div>
+</div>
+
+<p>Although the default implementation of {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()} saves useful information about
+your activity's UI, you still might need to override it to save additional information.
+For example, you might need to save member values that changed during the activity's life (which
+might correlate to values restored in the UI, but the members that hold those UI values are not
+restored, by default).</p>
+
+<p>Because the default implementation of {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()} helps save the state of the UI, if
+you override the method in order to save additional state information, you should always call the
+superclass implementation of {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()}
+before doing any work.</p>
+
+<p class="note"><strong>Note:</strong> Because {@link android.app.Activity#onSaveInstanceState
+onSaveInstanceState()} is not guaranteed
+to be called, you should use it only to record the transient state of the activity (the state of
+the UI)&mdash;you should never use it to store persistent data.  Instead, you should use  {@link
+android.app.Activity#onPause onPause()} to store persistent data (such as data that should be saved
+to a database) when the user leaves the activity.</p>
+
+<p>A good way to test your application's ability to restore its state is to simply rotate the
+device so that the screen orientation changes. When the screen orientation changes, the system
+destroys and recreates the activity in order to apply alternative resources that might be available
+for the new orientation. For this reason alone, it's very important that your activity
+completely restores its state when it is recreated, because users regularly rotate the screen while
+using applications.</p>
+
+
+<h3 id="ConfigurationChanges">Handling configuration changes</h3>
+
+<p>Some device configurations can change during runtime (such as screen orientation, keyboard
+availability, and language). When such a change occurs, Android restarts the running Activity
+({@link android.app.Activity#onDestroy} is called, followed immediately by {@link
+android.app.Activity#onCreate onCreate()}). The restart behavior is
+designed to help your application adapt to new configurations by automatically reloading your
+application with alternative resources that you've provided. If you design your activity to
+properly handle this event, it will be more resilient to unexpected events in the activity
+lifecycle.</p>
+
+<p>The best way to handle a configuration change, such as a change in the screen orientation, is
+  to simply preserve the state of your application using {@link
+  android.app.Activity#onSaveInstanceState onSaveInstanceState()} and {@link
+android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} (or {@link
+android.app.Activity#onCreate onCreate()}), as discussed in the previous section.</p>
+
+<p>For a detailed discussion about configuration changes that happen at runtime and how you should
+handle them, read <a href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling
+Runtime Changes</a>.</p>
+
+
+
+<h3 id="CoordinatingActivities">Coordinating activities</h3>
+
+ <p>When one activity starts another, they both experience lifecycle transitions. The first activity
+pauses and stops (though, it won't stop if it's still visible in the background), while the other
+activity is created. In case these activities share data saved to disc or elsewhere, it's important
+to understand that the first activity is not completely stopped before the second one is created.
+Rather, the process of starting the second one overlaps with the process of stopping the first
+one.</p>
+
+<p>The order of lifecycle callbacks is well defined, particularly when the two activities are in the
+same process and one is starting the other. Here's the order of operations that occur when Activity
+A starts Acivity B: </p>
+
+<ol>
+<li>Activity A's {@link android.app.Activity#onPause onPause()} method executes.</li>
+
+<li>Activity B's {@link android.app.Activity#onCreate onCreate()}, {@link
+android.app.Activity#onStart onStart()}, and {@link android.app.Activity#onResume onResume()}
+methods execute in sequence. (Activity B now has user focus.)</li>
+
+<li>Then, if Activity A is no longer visible on screen, its {@link
+android.app.Activity#onStop onStop()} method executes.</li>
+</ol>
+
+ <p>This predictable sequence of lifecycle callbacks allows you to manage the transition of
+information from one activity to another. For example, if you must write to a database when the
+first activity stops so that the following activity can read it, then you should write to the
+database during {@link android.app.Activity#onPause onPause()} instead of during {@link
+android.app.Activity#onStop onStop()}.</p>
+
+
+<h2>Beginner's Path</h2>
+
+<p>For more information about how Android maintains a history of activities and
+enables user multitasking, continue with the <b><a
+href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
+Stack</a></b> document.</p>
diff --git a/docs/html/guide/topics/fundamentals/tasks-and-back-stack.jd b/docs/html/guide/topics/fundamentals/tasks-and-back-stack.jd
new file mode 100644
index 0000000..47dc5471
--- /dev/null
+++ b/docs/html/guide/topics/fundamentals/tasks-and-back-stack.jd
@@ -0,0 +1,568 @@
+page.title=Tasks and Back Stack
+parent.title=Application Fundamentals
+parent.link=index.html
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+<h2>Quickview</h2>
+<ul>
+  <li>All activities belong to a task</li>
+  <li>A task contains a collection of activities in the order in which the user interacts with
+them</li>
+  <li>Tasks can move to the background and retain the state of each activity in order for the user
+to perform other tasks without loosing their work</li>
+</ul>
+
+<h2>In this document</h2>
+<ol>
+<li><a href="#ActivityState">Saving Activity State</a></li></li>
+<li><a href="#ManagingTasks">Managing Tasks</a>
+  <ol>
+    <li><a href="#TaskLaunchModes">Defining launch modes</a></li>
+    <li><a href="#Affinities">Handling affinities</a></li>
+    <li><a href="#Clearing">Clearing the back stack</a></li>
+    <li><a href="#Starting">Starting a task</a></li>
+  </ol>
+</li>
+</ol>
+
+<h2>See also</h2>
+<ol>
+  <li><a><a href="{@docRoot}videos/index.html#v=fL6gSd4ugSI">Application Lifecycle video</a></li>
+  <li><a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code &lt;activity&gt;} manifest
+element</a></li>
+</ol>
+</div>
+</div>
+
+
+<p>An application usually contains multiple <a
+href="{@docRoot}guide/topics/fundamentals/activities.html">activities</a>. Each activity
+should be designed around a specific kind of action the user can perform and can start other
+activities. For example, an email application might have one activity to show a list of new email.
+When the user selects an email, a new activity opens to view that email.</p>
+
+<p>An activity can even start activities that exist in other applications on the device. For
+example, if your application wants to send an email, you can define an intent to perform a "send"
+action and include some data, such as an email address and a message. An activity from another
+application that declares itself to handle this kind of intent then opens. In this case, the intent
+is to send an email, so an email application's "compose" activity starts (if multiple activities
+support the same intent, then the system lets the user select which one to use). When the email is
+sent, your activity resumes and it seems as if the email activity was part of your application. Even
+though the activities may be from different applications, Android maintains this seamless user
+experience by keeping both activities in the same <em>task</em>.</p>
+
+<p>A task is a collection of activities that users interact with
+when performing a certain job. The activities are arranged in a stack (the "back stack"), in the
+order in which each activity is opened.</p>
+
+<!-- SAVE FOR WHEN THE FRAGMENT DOC IS ADDED
+<div class="sidebox-wrapper">
+<div class="sidebox">
+<h3>Adding fragments to a task's back stack</h3>
+
+<p>Your activity can also include {@link android.app.Fragment}s to the back stack. For example,
+suppose you have a two-pane layout using fragments, one of which is a list view (fragment A) and the
+other being a layout to display an item from the list (fragment B). When the user selects an item
+from the list, fragment B is replaced by a new fragment (fragment C). In this case, it might be
+desireable for the user to navigate back to reveal fragment B, using the BACK key.</p>
+<p>In order to add fragment B to the back stack so that this is possible, you must call {@link
+android.app.FragmentTransaction#addToBackStack addToBackStack()} before you {@link
+android.app.FragmentTransaction#commit()} the transaction that replaces fragment B with fragment
+C.</p>
+<p>For more information about using fragments and adding them to the back stack, see the {@link
+android.app.Fragment} class documentation.</p>
+
+</div>
+</div>
+-->
+
+<p>The device Home screen is the starting place for most tasks. When the user touches an icon in the
+application
+launcher (or a shortcut on the Home screen), that application's task comes to the foreground. If no
+task exists for the application (the application has not been used recently), then a new task
+is created and the "main" activity for that application opens as the root activity in the stack.</p>
+
+<p>When the current activity starts another, the new activity is pushed on the top of the stack and
+takes focus. The previous activity remains in the stack, but is stopped. When an activity
+stops, the system retains the current state of its user interface. When the user presses the BACK
+key, the current activity is popped from the top of the stack (the activity is destroyed) and the
+previous activity resumes (the previous state of its UI is restored). Activities in the stack are
+never rearranged, only pushed and popped from the stack&mdash;pushed onto the stack when started by
+the current activity and popped off when the user leaves it using the BACK key. As such, the back
+stack operates as a "last in, first out" object structure. Figure 1 visualizes
+this behavior with a timeline showing the progress between activities along with the current back
+stack at each point in time.</p>
+
+<img src="{@docRoot}images/fundamentals/diagram_backstack.png" alt="" />
+<p class="img-caption"><strong>Figure 1.</strong> A representation of how each new activity in a
+task adds an item to the back stack. When the user presses the BACK key, the current activity is
+destroyed and the previous activity resumes.</p>
+
+
+<p>If the user continues to press BACK, then each activity in the stack is popped off to reveal the
+previous one, until the user returns to the Home screen (or to whichever activity was running when
+the task began). When all activities are removed from the stack, the task no longer exists.</p>
+
+<div class="figure" style="width:369px">
+<img src="{@docRoot}images/fundamentals/diagram_multitasking.png" alt="" /> <p
+class="img-caption"><strong>Figure 2.</strong> Two tasks: Task A is in the background, waiting
+to be resumed, while Task B receives user interaction in the foreground.</p>
+</div>
+<div class="figure" style="width:178px">
+  <img src="{@docRoot}images/fundamentals/diagram_multiple_instances.png" alt="" /> <p
+class="img-caption"><strong>Figure 3.</strong> A single activity is instantiated multiple times.</p>
+</div>
+
+<p>A task is a cohesive unit that can move to the "background" when users begin a new task or go
+to the Home screen, via the HOME key. While in the background, all the activities in the task are
+stopped, but the back stack for the task remains intact&mdash;the task has simply lost focus while
+another task takes place, as shown in figure 2. A task can then return to the "foreground" so users
+can pick up where they left off. Suppose, for example, that the current task (Task A) has three
+activities in its stack&mdash;two under the current activity. The user presses the HOME key, then
+starts a new application from the application launcher. When the Home screen appears, Task A goes
+into the background. When the new application starts, the system starts a task for that application
+(Task B) with its own stack of activities. After interacting with
+that application, the user returns Home again and selects the application that originally
+started Task A. Now, Task A comes to the
+foreground&mdash;all three activities in its stack are intact and the activity at the top of the
+stack resumes. At
+this point, the user can also switch back to Task B by going Home and selecting the application icon
+that started that task (or by touching and holding the HOME key to reveal recent tasks and selecting
+one). This is an example of multitasking on Android.</p>
+
+<p class="note"><strong>Note:</strong> Multiple tasks can be held in the background at once.
+However, if the user is running many background tasks at the same time, the system might begin
+destroying background activities in order to recover memory, causing the activity states to be lost.
+See the following section about <a href="#ActivityState">Activity state</a>.</p>
+
+<p>Because the activities in the back stack are never rearranged, if your application allows
+users to start a particular activity from more than one activity, a new instance of
+that activity is created and popped onto the stack (rather than bringing any previous instance of
+the activity to the top). As such, one activity in your application might be instantiated multiple
+times (even from different tasks), as shown in figure 3. As such, if the user navigates backward
+using the BACK key, each instance of the activity is revealed in the order they were opened (each
+with their own UI state). However, you can modify this behavior if you do not want an activity to be
+instantiated more than once. How to do so is discussed in the later section about <a
+href="#ManagingTasks">Managing Tasks</a>.</p>
+
+
+<p>To summarize the default behavior for activities and tasks:</p>
+
+<ul>
+  <li>When Activity A starts Activity B, Activity A is stopped, but the system retains its state
+(such as scroll position and text entered into forms).
+If the user presses the BACK key while in Activity B, Activity A resumes with its state
+restored.</li>
+  <li>When the user leaves a task by pressing the HOME key, the current activity is stopped and
+its task goes into the background. The system retains the state of every activity in the task. If
+the user later resumes the task by selecting the launcher icon that began the task, the task comes
+to the foreground and resumes the activity at the top of the stack.</li>
+  <li>If the user presses the BACK key, the current activity is popped from the stack and
+destroyed. The previous activity in the stack is resumed. When an activity is destroyed, the system
+<em>does not</em> retain the activity's state.</li>
+  <li>Activities can be instantiated multiple times, even from other tasks.</li>
+</ul>
+
+
+<h2 id="ActivityState">Saving Activity State</h2>
+
+<p>As discussed above, the system's default behavior preserves the state of an activity when it is
+stopped. This way, when users navigate back to a previous activity, its user interface appears
+the way they left it. However, you can&mdash;and <strong>should</strong>&mdash;proactively retain
+the state of your activities using callback methods, in case the activity is destroyed and must
+be recreated.</p>
+
+<p>When the system stops one of your activities (such as when a new activity starts or the task
+moves to the background), the system might destroy that activity completely if it needs to recover
+system memory. When this happens, information about the activity state is lost. If this happens, the
+system still
+knows that the activity has a place in the back stack, but when the activity is brought to the
+top of the stack the system must recreate it (rather than resume it). In order to
+avoid loosing the user's work, you should proactively retain it by implementing the {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()} callback
+methods in your activity.</p>
+
+<p>For more information about how to save your activity state, see the <a
+href="{@docRoot}guide/topics/fundamentals/activities.html#SavingActivityState">Activities</a>
+document.</p>
+
+
+
+<h2 id="ManagingTasks">Managing Tasks</h2>
+
+<p>The way Android manages tasks and the back stack, as described above&mdash;by placing all
+activities started in succession in the same task and in a "last in, first out" stack&mdash;works
+great for most applications and you shouldn't have to worry about how your activities are associated
+with tasks or how they exist in the back stack. However, you might decide that you want to interrupt
+the normal behavior. Perhaps you want an activity in your application to begin a new task when it is
+started (instead of being placed within the current task); or, when you start an activity, you want
+to bring forward an existing instance of it (instead of creating a new
+instance on top of the back stack); or, you want your back stack to be cleared of all
+activitiesstart an activity except for the root activity when the user leaves the task.</p>
+
+<p>You can do these things and more, with attributes in the
+<a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code
+&lt;activity&gt;}</a> manifest element and with flags in the intent that you pass to {@link
+android.app.Activity#startActivity startActivity()}.</p>
+
+<p>In this regard, the the principal <a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code &lt;activity&gt;}</a>
+attributes you can use are:</p>
+
+<ul class="nolist">
+  <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code
+taskAffinity}</a></li>
+  <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code
+launchMode}</a></li>
+  <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#reparent">{@code
+allowTaskReparenting}</a></li>
+  <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#clear">{@code
+clearTaskOnLaunch}</a></li>
+  <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#always">{@code
+alwaysRetainTaskState}</a></li>
+  <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#finish">{@code
+finishOnTaskLaunch}</a></li>
+</ul>
+
+<p>And the principal intent flags you can use are:</p>
+
+<ul class="nolist">
+  <li>{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}</li>
+  <li>{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}</li>
+  <li>{@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}</li>
+</ul>
+
+<p>In the following sections, you'll see how you can use these manifest attributes and intent
+flags to define how activities are associated with tasks and how the behave in the back stack.</p>
+
+
+<p class="caution"><strong>Caution:</strong> Most applications should not interrupt the default
+behavior for activities and tasks. If you determine that it's necessary for your activity to modify
+the default behaviors, use caution and be sure to test the usability of the activity during
+launch and when navigating back to it from other activities and tasks with the BACK key. Be sure 
+to test for navigation behaviors that might conflict with the user's expected behavior.</p>
+
+
+<h3 id="TaskLaunchModes">Defining launch modes</h3>
+
+<p>Launch modes allow you to define how a new instance of an activity is associated with the
+current task. You can define different launch modes in two ways:</p>
+<ul class="nolist">
+  <li><a href="#ManifestForTasks">Using the manifest file</a>
+    <p>When you declare an activity in your manifest file, you can specify how the activity
+should associate with tasks when it starts.</li>
+  <li><a href="#IntentFlagsForTasks">Using Intent flags</a>
+    <p>When you call {@link android.app.Activity#startActivity startActivity()},
+you can include a flag in the {@link android.content.Intent} that declares how (or
+whether) the new activity should associate with the current task.</p></li>
+</ul>
+
+<p>As such, if Activity A starts Activity B, Activity B can define in its manifest how it
+should associate with the current task (if at all) and Activity A can also request how Activity
+B should associate with current task. If both activities define how Activity B
+should associate with a task, then Activity A's request (as defined in the intent) is honored
+over Activity B's request (as defined in its manifest).</p>
+
+<p class="note"><strong>Note:</strong> Some the launch modes available in the manifest
+are not available as flags for an intent and, likewise, some launch modes available as flags
+for an intent cannot be defined in the manifest.</p>
+
+
+<h4 id="ManifestForTasks">Using the manifest file</h4>
+
+<p>When declaring an activity in your manifest file, you can specify how the activity should
+associate with a task using the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code &lt;activity&gt;}</a>
+element's <a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code
+launchMode}</a> attribute.</p>
+
+<p>The <a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code
+launchMode}</a> attribute specifies an instruction on how the activity should be launched into a
+task. There are four different launch modes you can assign to the
+<code><a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">launchMode</a></code>
+attribute:</p>
+
+<dl>
+<dt>{@code "standard"} (the default mode)</dt>
+  <dd>Default. The system creates a new instance of the activity in the task from
+which it was started and routes the intent to it. The activity can be instantiated multiple times,
+each instance can belong to different tasks, and one task can have multiple instances.</dd>
+<dt>{@code "singleTop"}</dt>
+  <dd>If an instance of the activity already exists at the top of the current task, the system
+routes the intent to that instance through a call to its {@link
+android.app.Activity#onNewIntent onNewIntent()} method, rather than creating a new instance of the
+activity. The activity can be instantiated multiple times, each instance can
+belong to different tasks, and one task can have multiple instances (but only if the the
+activity at the top of the back stack is <em>not</em> an existing instance of the activity).
+  <p>For example, suppose a task's back stack consists of root activity A with activities B, C,
+and D on top (the stack is A-B-C-D; D is on top). An intent arrives for an activity of type D.
+If D has the default {@code "standard"} launch mode, a new instance of the class is launched and the
+stack becomes A-B-C-D-D. However, if D's launch mode is {@code "singleTop"}, the existing instance
+of D is deliverd the intent through {@link
+android.app.Activity#onNewIntent onNewIntent()}, because it's at the top of the stack&mdash;the
+stack remains A-B-C-D. However, if an intent arrives for an activity of type B, then a new
+instance of B is added to the stack, even if its launch mode is {@code "singleTop"}.</p>
+  <p class="note"><strong>Note:</strong> When a new instance of an activity is created,
+the user can press the BACK key to return to the previous activity. But when an existing instance of
+an activity handles a new intent, the user cannot press the BACK key to return to the state of
+the activity before the new intent arrived in {@link android.app.Activity#onNewIntent
+onNewIntent()}.</p>
+</dd>
+
+<dt>{@code "singleTask"}</dt>
+  <dd>The system creates a new task and instantiates the activity at the root of the new task.
+However, if an instance of the activity already exists in a separate task, the system routes the
+intent to the existing instance through a call to its {@link
+android.app.Activity#onNewIntent onNewIntent()} method, rather than creating a new instance. Only
+one instance of the activity can exist at a time.
+  <p class="note"><strong>Note:</strong> Although the activity starts in a new task, the
+BACK key still returns the user to the previous activity.</p></dd>
+<dt>{@code "singleInstance"}.</dt>
+  <dd>Same as {@code "singleTask"}, except that the system doesn't launch any other activities into
+the task holding the instance. The activity is always the single and only member of its task;
+any activities started by this one open in a separate task.</dd>
+</dl>
+
+
+<p>As another example, the Android Browser application declares that the web browser activity should
+always open in its own task&mdash;by specifying the {@code singleTask} launch mode in the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code &lt;activity&gt;}</a> element.
+This means that if your application issues an
+intent to open the Android Browser, its activity is <em>not</em> placed in the same
+task as your application. Instead, either a new task starts for the Browser or, if the Browser
+already has a task running in the background, that task is brought forward to handle the new
+intent.</p>
+
+<p>Regardless of whether an activity starts in a new task or in the same task as the activity that
+started it, the BACK key always takes the user to the previous activity. However, if you
+start an activity from your task (Task A) that specifies the {@code singleTask} launch mode, then
+that activity might have an instance in the background that belongs to a task with its own back
+stack (Task B). In this
+case, when Task B is brought forward to handle a new intent, the BACK key first navigates
+backward through the activities in Task B before returning to
+the top-most activity in Task A. Figure 4 visualizes this type of scenario.</p>
+
+<img src="{@docRoot}images/fundamentals/diagram_backstack_singletask_multiactivity.png" alt="" />
+<p class="img-caption"><strong>Figure 4.</strong> A representation of how an activity with
+launch mode "singleTask" is added to the back stack. If the activity is already a part of a
+background task with its own back stack (Task B), then the entire back stack also comes
+forward, on top of the current task (Task A).</p>
+
+<p>For more information about using launch modes in the manifest file, see the
+<code><a href="{@docRoot}guide/topics/manifest/activity-element.html">&lt;activity&gt;</a></code>
+element documentation, where the {@code launchMode} attribute and the accepted values are
+discussed more.</p>
+
+<p class="note"><strong>Note:</strong> The behaviors that you specify for your activity with the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a> attribute
+can be overriden by flags included with the intent that start your activity, as discussed in the
+next section.</p>
+
+
+
+<h4 id="#IntentFlagsForTasks">Using Intent flags</h4>
+
+<p>When starting an activity, you can modify the default association of an activity to its task
+by including flags in the intent that you deliver to {@link
+android.app.Activity#startActivity startActivity()}. The flags you can use to modify the
+default behavior are:</p>
+
+<p>
+  <dt>{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}</dt>
+    <dd>Start the activity in a new task. If a task is already running for the activity you are now
+starting, that task is brought to the foreground with its last state restored and the activity
+receives the new intent in {@link android.app.Activity#onNewIntent onNewIntent()}. 
+    <p>This produces the same behavior as the {@code "singleTask"} <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a> value,
+discussed in the previous section.</p></dd>
+  <dt>{@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}</dt>
+    <dd>If the activity being started is the current activity (at the top of the back stack), then
+the existing instance receives a call to {@link android.app.Activity#onNewIntent onNewIntent()},
+instead of creating a new instance of the activity.
+    <p>This produces the same behavior as the {@code "singleTop"} <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a> value,
+discussed in the previous section.</p></dd>
+  <dt>{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}</dt>
+    <dd>If the activity being started is already running in the current task, then instead
+of launching a new instance of that activity, all of the other activities on top of it are
+destroyed and this intent is delivered to the resumed instance of the activity (now on top),
+through {@link android.app.Activity#onNewIntent onNewIntent()}).
+    <p>There is no value for the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a>
+attribute that produces this behavior.</p>
+    <p>{@code FLAG_ACTIVITY_CLEAR_TOP} is most often used in conjunction with {@code
+FLAG_ACTIVITY_NEW_TASK}.  When used together, these flags are a way of locating an existing activity
+in another task and putting it in a position where it can respond to the intent. </p>
+    <p class="note"><strong>Note:</strong> If the launch mode of the designated activity is {@code
+"standard"}, it too is removed from the stack and a new instance is launched in its place to handle
+the incoming intent.  That's because a new instance is always created for a new intent when the
+launch mode is {@code "standard"}. </p>
+</dd>
+</dl>
+
+
+
+
+
+<h3 id="Affinities">Handling affinities</h3>
+
+<p>The <em>affinity</em> indicates which task an activity prefers to belong to. By default, all the
+activities from the same application have an affinity for each other. So, by default, all
+activities in the same application prefer to be in the same task. However, you can modify
+the default affinity for an activity. Activities defined in
+different applications can share an affinity, or activities defined in the same application can be
+assigned different task affinities.</p>
+
+<p>You can modify the affinity for any given activity with the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code taskAffinity}</a> attribute
+of the <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code &lt;activity&gt;}</a>
+element.</p>
+
+<p>The <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code taskAffinity}</a>
+attribute takes a string value, which must be unique from the default package name
+declared in the <a href="{@docRoot}guide/topics/manifest/manifest-element.html">{@code
+&lt;manifest&gt;}</a> element, because the system uses that name to identify the default task
+affinity for the application.</p>
+
+<p>The affinity comes into play in two circumstances:</p>
+<ul>
+  <li>When the intent that launches an activity contains the {@link
+android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag.
+
+<p>A new activity is, by default, launched into the task of the activity
+that called {@link android.app.Activity#startActivity startActivity()}. It's pushed onto the same
+back stack as the caller.  However, if the intent passed to {@link
+android.app.Activity#startActivity startActivity()} contains the {@link
+android.content.Intent#FLAG_ACTIVITY_NEW_TASK}
+flag, the system looks for a different task to house the new activity. Often, it's a new task. 
+However, it doesn't have to be.  If there's already an existing task with the same affinity as the
+new activity, the activity is launched into that task.  If not, it begins a new task.</p>
+
+<p>If this flag causes an activity to begin a new task and the user presses the HOME key to leave
+it, there must be some way for the user to navigate back to the task. Some entities (such as the
+notification manager) always start activities in an external task, never as part of their own, so
+they always put {@code FLAG_ACTIVITY_NEW_TASK} in the intents they pass to {@link
+android.app.Activity#startActivity startActivity()}.  If you have an activity that can be invoked by
+an external entity that might use this flag, take care that the user has a independent way to get
+back to the task that's started, such as with a launcher icon (the root activity of the task
+has a {@link android.content.Intent#CATEGORY_LAUNCHER} intent filter; see the <a
+href="#Starting">Starting a task</a> section below).</p>
+</li>
+
+  <li>When an activity has its <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#reparent">{@code
+allowTaskReparenting}</a> attribute set to {@code "true"}.
+  <p>In this case, the activity can move from the task it starts to the task it has an affinity
+for, when that task comes to the foreground.</p>
+  <p>For example, suppose that an activity that reports weather conditions in selected cities is
+defined as part of a travel application.  It has the same affinity as other activities in the same
+application (the default application affinity) and it allows re-parenting with this attribute.
+When one of your activities starts the weather reporter activity, it initially belongs to the same
+task as your activity. However, when the travel application's task comes to the foreground, the
+weather reporter activity is reassigned to that task and displayed within it.</p>
+</li>
+</ul>
+
+<p class="note"><strong>Tip:</strong> If an {@code .apk} file contains more than one "application"
+from the user's point of view, you probably want to use the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code taskAffinity}</a>
+attribute to assign different affinities to the activities associated with each "application".</p>
+
+
+
+<h3 id="Clearing">Clearing the back stack</h3>
+
+<p>If the user leaves a task for a long time, the system clears the task of all activities except
+the root activity.  When the user returns to the task again, only the root activity is restored.
+The system behaves this way, because, after an extended amount of time, users likely have abandoned
+what they were doing before and are returning to the task to begin something new. </p>
+
+<p>There are some activity attributes that you can use to modify this behavior: </p>
+
+<dl>
+<dt><code><a
+href="{@docRoot}guide/topics/manifest/activity-element.html#always">alwaysRetainTaskState</a></code>
+</dt>
+<dd>If this attribute is set to {@code "true"} in the root activity of a task,
+the default behavior just described does not happen.
+The task retains all activities in its stack even after a long period.</dd>
+
+<dt><code><a
+href="{@docRoot}guide/topics/manifest/activity-element.html#clear">clearTaskOnLaunch</a></code></dt>
+<dd>If this attribute is set to {@code "true"} in the root activity of a task,
+the stack is cleared down to the root activity whenever the user leaves the task
+and returns to it.  In other words, it's the opposite of <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#always">{@code
+alwaysRetainTaskState}</a>.  The user always returns to the task in its
+initial state, even after a leaving the task for only a moment.</dd>
+
+<dt><code><a
+href="{@docRoot}guide/topics/manifest/activity-element.html#finish">finishOnTaskLaunch</a></code>
+</dt>
+<dd>This attribute is like <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#clear">{@code clearTaskOnLaunch}</a>,
+but it operates on a
+single activity, not an entire task.  It can also cause any activity to go
+away, including the root activity.  When it's set to {@code "true"}, the
+activity remains part of the task only for the current session.  If the user
+leaves and then returns to the task, it is no longer present.</dd>
+</dl>
+
+
+
+
+<h3 id="Starting">Starting a task</h3>
+
+<p>You can set up an activity as the entry point for a task by giving it an intent filter with
+{@code "android.intent.action.MAIN"} as the specified action and {@code
+"android.intent.category.LAUNCHER"} as the specified category. For example:</p>
+
+<pre>
+&lt;activity ... &gt;
+    &lt;intent-filter ... &gt;
+        &lt;action android:name="android.intent.action.MAIN" /&gt;
+        &lt;category android:name="android.intent.category.LAUNCHER" /&gt;
+    &lt;/intent-filter&gt;
+    ...
+&lt;/activity&gt;
+</pre>
+
+<p>An intent filter of this kind causes an icon and label for the
+activity to be displayed in the application launcher, giving users a way to launch the activity and
+to return to the task that it creates any time after it has been launched.
+</p>
+
+<p>This second ability is important: Users must be able to leave a task and then come back to it
+later using this activity launcher.  For this reason, the two <a href="#LaunchModes">launch
+modes</a> that mark activities as always initiating a task, {@code "singleTask"} and "{@code
+"singleInstance"}, should be used only when the activity has an {@link
+android.content.Intent#ACTION_MAIN}
+and a {@link android.content.Intent#CATEGORY_LAUNCHER}
+filter. Imagine, for example, what could happen if the filter is missing: An intent launches a
+{@code "singleTask"} activity, initiating a new task, and the user spends some time working in
+that task.  The user then presses the HOME key. The task is now sent to the background and not
+visible. Because it is not represented in the application launcher, the user has no way to return to
+the task.
+</p>
+
+<p>For those cases where you don't want the user to be able to return to an activity, set the
+  <code><a
+href="{@docRoot}guide/topics/manifest/activity-element.html">&lt;activity&gt;</a></code> element's
+<a href="{@docRoot}guide/topics/manifest/activity-element.html#finish">{@code
+finishOnTaskLaunch}</a> to {@code "true"} (see <a
+href="#Clearing">Clearing the stack</a>).</p>
+
+
+
+
+<h2>Beginner's Path</h2>
+
+<p>For more information about how to use intents to
+activate other application components and publish the intents to which your components
+respond, continue with the <b><a
+href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent
+Filters</a></b> document.</p>
diff --git a/docs/html/images/fundamentals/diagram_backstack.png b/docs/html/images/fundamentals/diagram_backstack.png
new file mode 100644
index 0000000..2c6e33f
--- /dev/null
+++ b/docs/html/images/fundamentals/diagram_backstack.png
Binary files differ
diff --git a/docs/html/images/fundamentals/diagram_backstack_singletask_multiactivity.png b/docs/html/images/fundamentals/diagram_backstack_singletask_multiactivity.png
new file mode 100644
index 0000000..d6a21d7
--- /dev/null
+++ b/docs/html/images/fundamentals/diagram_backstack_singletask_multiactivity.png
Binary files differ
diff --git a/docs/html/images/fundamentals/diagram_multiple_instances.png b/docs/html/images/fundamentals/diagram_multiple_instances.png
new file mode 100644
index 0000000..380e7788
--- /dev/null
+++ b/docs/html/images/fundamentals/diagram_multiple_instances.png
Binary files differ
diff --git a/docs/html/images/fundamentals/diagram_multitasking.png b/docs/html/images/fundamentals/diagram_multitasking.png
new file mode 100644
index 0000000..b8c7b45
--- /dev/null
+++ b/docs/html/images/fundamentals/diagram_multitasking.png
Binary files differ
diff --git a/docs/html/images/fundamentals/restore_instance.png b/docs/html/images/fundamentals/restore_instance.png
new file mode 100644
index 0000000..fa428a7
--- /dev/null
+++ b/docs/html/images/fundamentals/restore_instance.png
Binary files differ
diff --git a/docs/html/images/home/market-intl.png b/docs/html/images/home/market-intl.png
new file mode 100644
index 0000000..2bb22f1
--- /dev/null
+++ b/docs/html/images/home/market-intl.png
Binary files differ
diff --git a/docs/html/index.jd b/docs/html/index.jd
index f37a122..049df62 100644
--- a/docs/html/index.jd
+++ b/docs/html/index.jd
@@ -11,12 +11,14 @@
                             </div><!-- end homeTitle -->
                             <div id="announcement-block">
                             <!-- total max width is 520px -->
-                                  <img src="{@docRoot}images/home/io-logo.png" alt="Google IO
-2010" width="200" height="41" style="padding:22px 12px;"/>
+                                  <img src="{@docRoot}images/home/market-intl.png" alt="Android
+Market" width="104" height="120" style="padding:10px 60px 5px" />
                                   <div id="announcement" style="width:295px">
-<p>Thanks to everyone who visited us at Google I/O in San Francisco! Stay tuned for
-videos and slides from the Android sessions, which will be posted at the Google I/O web site.</p><p><a
-href="http://code.google.com/events/io/2010/sessions.html#Android">Learn more &raquo;</a></p>
+<p>We're pleased to announce that paid apps are available in more locations of the world! Developers
+from 20 more locations can now sell paid apps on Android Market. Users in more locations will also
+soon be able to purchase apps.</p><p><a
+href="http://android-developers.blogspot.com/2010/09/more-countries-more-sellers-more-buyers.html">
+Learn more &raquo;</a></p>
                                 </div> <!-- end annoucement -->
                             </div> <!-- end annoucement-block -->
                         </div><!-- end topAnnouncement -->
diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java
index 985d700..2c076b3 100644
--- a/graphics/java/android/renderscript/Allocation.java
+++ b/graphics/java/android/renderscript/Allocation.java
@@ -182,6 +182,24 @@
         mRS.nAllocationRead(mID, d);
     }
 
+    public void resize(int dimX) {
+        if ((mType.getY() > 0)|| (mType.getZ() > 0) || mType.getFaces() || mType.getLOD()) {
+            throw new IllegalStateException("Resize only support for 1D allocations at this time.");
+        }
+        mRS.nAllocationResize1D(mID, dimX);
+    }
+
+    /*
+    public void resize(int dimX, int dimY) {
+        if ((mType.getZ() > 0) || mType.getFaces() || mType.getLOD()) {
+            throw new IllegalStateException("Resize only support for 2D allocations at this time.");
+        }
+        if (mType.getY() == 0) {
+            throw new IllegalStateException("Resize only support for 2D allocations at this time.");
+        }
+        mRS.nAllocationResize2D(mID, dimX, dimY);
+    }
+    */
 
     public class Adapter1D extends BaseObj {
         Adapter1D(int id, RenderScript rs) {
diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java
index 3c0b4e5..0088373 100644
--- a/graphics/java/android/renderscript/RenderScript.java
+++ b/graphics/java/android/renderscript/RenderScript.java
@@ -254,6 +254,15 @@
         return rsnAllocationGetType(mContext, id);
     }
 
+    native void rsnAllocationResize1D(int con, int id, int dimX);
+    synchronized void nAllocationResize1D(int id, int dimX) {
+        rsnAllocationResize1D(mContext, id, dimX);
+    }
+    native void rsnAllocationResize2D(int con, int id, int dimX, int dimY);
+    synchronized void nAllocationResize2D(int id, int dimX, int dimY) {
+        rsnAllocationResize2D(mContext, id, dimX, dimY);
+    }
+
     native int  rsnFileA3DCreateFromAssetStream(int con, int assetStream);
     synchronized int nFileA3DCreateFromAssetStream(int assetStream) {
         return rsnFileA3DCreateFromAssetStream(mContext, assetStream);
diff --git a/graphics/jni/android_renderscript_RenderScript.cpp b/graphics/jni/android_renderscript_RenderScript.cpp
index 67a2b63..8f1e93c 100644
--- a/graphics/jni/android_renderscript_RenderScript.cpp
+++ b/graphics/jni/android_renderscript_RenderScript.cpp
@@ -587,6 +587,20 @@
     return (jint) rsAllocationGetType(con, (RsAllocation)a);
 }
 
+static void
+nAllocationResize1D(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jint dimX)
+{
+    LOG_API("nAllocationResize1D, con(%p), alloc(%p), sizeX(%i)", con, (RsAllocation)alloc, dimX);
+    rsAllocationResize1D(con, (RsAllocation)alloc, dimX);
+}
+
+static void
+nAllocationResize2D(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jint dimX, jint dimY)
+{
+    LOG_API("nAllocationResize1D, con(%p), alloc(%p), sizeX(%i), sizeY(%i)", con, (RsAllocation)alloc, dimX, dimY);
+    rsAllocationResize2D(con, (RsAllocation)alloc, dimX, dimY);
+}
+
 // -----------------------------------
 
 static int
@@ -1252,6 +1266,8 @@
 {"rsnAllocationRead",                "(II[I)V",                               (void*)nAllocationRead_i },
 {"rsnAllocationRead",                "(II[F)V",                               (void*)nAllocationRead_f },
 {"rsnAllocationGetType",             "(II)I",                                 (void*)nAllocationGetType},
+{"rsnAllocationResize1D",            "(III)V",                                (void*)nAllocationResize1D },
+{"rsnAllocationResize2D",            "(IIII)V",                               (void*)nAllocationResize2D },
 
 {"rsnAdapter1DBindAllocation",       "(III)V",                                (void*)nAdapter1DBindAllocation },
 {"rsnAdapter1DSetConstraint",        "(IIII)V",                               (void*)nAdapter1DSetConstraint },
diff --git a/include/utils/ZipFileRO.h b/include/utils/ZipFileRO.h
index e1ff780..3c1f3ca 100644
--- a/include/utils/ZipFileRO.h
+++ b/include/utils/ZipFileRO.h
@@ -14,13 +14,19 @@
  * limitations under the License.
  */
 
-//
-// Read-only access to Zip archives, with minimal heap allocation.
-//
-// This is similar to the more-complete ZipFile class, but no attempt
-// has been made to make them interchangeable.  This class operates under
-// a very different set of assumptions and constraints.
-//
+/*
+ * Read-only access to Zip archives, with minimal heap allocation.
+ *
+ * This is similar to the more-complete ZipFile class, but no attempt
+ * has been made to make them interchangeable.  This class operates under
+ * a very different set of assumptions and constraints.
+ *
+ * One such assumption is that if you're getting file descriptors for
+ * use with this class as a child of a fork() operation, you must be on
+ * a pread() to guarantee correct operation. This is because pread() can
+ * atomically read at a file offset without worrying about a lock around an
+ * lseek() + read() pair.
+ */
 #ifndef __LIBS_ZIPFILERO_H
 #define __LIBS_ZIPFILERO_H
 
@@ -55,6 +61,10 @@
  * the record structure.  However, this requires a private mapping of
  * every page that the Central Directory touches.  Easier to tuck a copy
  * of the string length into the hash table entry.
+ *
+ * NOTE: If this is used on file descriptors inherited from a fork() operation,
+ * you must be on a platform that implements pread() to guarantee correctness
+ * on the shared file descriptors.
  */
 class ZipFileRO {
 public:
diff --git a/libs/binder/CursorWindow.cpp b/libs/binder/CursorWindow.cpp
index 20b27c9..bdd4dd6 100644
--- a/libs/binder/CursorWindow.cpp
+++ b/libs/binder/CursorWindow.cpp
@@ -141,10 +141,12 @@
     size = requestedSize + padding;
 
     if (size > freeSpace()) {
-        LOGE("need to grow: mSize = %d, size = %d, freeSpace() = %d, numRows = %d", mSize, size, freeSpace(), mHeader->numRows);
+        LOGV("need to grow: mSize = %d, size = %d, freeSpace() = %d, numRows = %d", mSize, size,
+                freeSpace(), mHeader->numRows);
         // Only grow the window if the first row doesn't fit
         if (mHeader->numRows > 1) {
-LOGE("not growing since there are already %d row(s), max size %d", mHeader->numRows, mMaxSize);
+            LOGV("not growing since there are already %d row(s), max size %d", mHeader->numRows,
+                    mMaxSize);
             return 0;
         }
 
diff --git a/libs/hwui/FboCache.cpp b/libs/hwui/FboCache.cpp
index 77fbda2..2ef71c2 100644
--- a/libs/hwui/FboCache.cpp
+++ b/libs/hwui/FboCache.cpp
@@ -16,6 +16,8 @@
 
 #define LOG_TAG "OpenGLRenderer"
 
+#include <stdlib.h>
+
 #include "FboCache.h"
 #include "Properties.h"
 
@@ -57,14 +59,31 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void FboCache::clear() {
-
+    for (size_t i = 0; i < mCache.size(); i++) {
+        const GLuint fbo = mCache.itemAt(i);
+        glDeleteFramebuffers(1, &fbo);
+    }
+    mCache.clear();
 }
 
 GLuint FboCache::get() {
-    return 0;
+    GLuint fbo;
+    if (mCache.size() > 0) {
+        fbo = mCache.itemAt(mCache.size() - 1);
+        mCache.removeAt(mCache.size() - 1);
+    } else {
+        glGenFramebuffers(1, &fbo);
+    }
+    return fbo;
 }
 
 bool FboCache::put(GLuint fbo) {
+    if (mCache.size() < mMaxSize) {
+        mCache.add(fbo);
+        return true;
+    }
+
+    glDeleteFramebuffers(1, &fbo);
     return false;
 }
 
diff --git a/libs/hwui/FboCache.h b/libs/hwui/FboCache.h
index 66f66ea..ec4afe9 100644
--- a/libs/hwui/FboCache.h
+++ b/libs/hwui/FboCache.h
@@ -21,8 +21,6 @@
 
 #include <utils/SortedVector.h>
 
-#include "GenerationCache.h"
-
 namespace android {
 namespace uirenderer {
 
diff --git a/libs/hwui/GenerationCache.h b/libs/hwui/GenerationCache.h
index 070e33f..35c6bea 100644
--- a/libs/hwui/GenerationCache.h
+++ b/libs/hwui/GenerationCache.h
@@ -65,6 +65,7 @@
     void put(K key, V value);
     V remove(K key);
     V removeOldest();
+    V getValueAt(uint32_t index) const;
 
     uint32_t size() const;
 
@@ -128,6 +129,11 @@
 }
 
 template<typename K, typename V>
+V GenerationCache<K, V>::getValueAt(uint32_t index) const {
+    return mCache.valueAt(index);
+}
+
+template<typename K, typename V>
 V GenerationCache<K, V>::get(K key) {
     ssize_t index = mCache.indexOfKey(key);
     if (index >= 0) {
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index a0cc5d6..6024765 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -32,21 +32,14 @@
  * Dimensions of a layer.
  */
 struct LayerSize {
-    LayerSize(): width(0), height(0), id(0) { }
-    LayerSize(const uint32_t width, const uint32_t height): width(width), height(height), id(0) { }
-    LayerSize(const LayerSize& size): width(size.width), height(size.height), id(size.id) { }
+    LayerSize(): width(0), height(0) { }
+    LayerSize(const uint32_t width, const uint32_t height): width(width), height(height) { }
+    LayerSize(const LayerSize& size): width(size.width), height(size.height) { }
 
     uint32_t width;
     uint32_t height;
 
-    // Incremental id used by the layer cache to store multiple
-    // LayerSize with the same dimensions
-    uint32_t id;
-
     bool operator<(const LayerSize& rhs) const {
-        if (id != 0 && rhs.id != 0 && id != rhs.id) {
-            return id < rhs.id;
-        }
         if (width == rhs.width) {
             return height < rhs.height;
         }
@@ -54,12 +47,12 @@
     }
 
     bool operator==(const LayerSize& rhs) const {
-        return id == rhs.id && width == rhs.width && height == rhs.height;
+        return width == rhs.width && height == rhs.height;
     }
 }; // struct LayerSize
 
 /**
- * A layer has dimensions and is backed by an OpenGL texture.
+ * A layer has dimensions and is backed by an OpenGL texture or FBO.
  */
 struct Layer {
     /**
@@ -71,6 +64,11 @@
      */
     GLuint texture;
     /**
+     * Name of the FBO used to render the layer. If the name is 0
+     * this layer is not backed by an FBO, but a simple texture.
+     */
+    GLuint fbo;
+    /**
      * Opacity of the layer.
      */
     int alpha;
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index 8c70cf9..2183718 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -32,7 +32,7 @@
 
 LayerCache::LayerCache():
         mCache(GenerationCache<LayerSize, Layer*>::kUnlimitedCapacity),
-        mIdGenerator(1), mSize(0), mMaxSize(MB(DEFAULT_LAYER_CACHE_SIZE)) {
+        mSize(0), mMaxSize(MB(DEFAULT_LAYER_CACHE_SIZE)) {
     char property[PROPERTY_VALUE_MAX];
     if (property_get(PROPERTY_LAYER_CACHE_SIZE, property, NULL) > 0) {
         LOGD("  Setting layer cache size to %sMB", property);
@@ -44,7 +44,7 @@
 
 LayerCache::LayerCache(uint32_t maxByteSize):
         mCache(GenerationCache<LayerSize, Layer*>::kUnlimitedCapacity),
-        mIdGenerator(1), mSize(0), mMaxSize(maxByteSize) {
+        mSize(0), mMaxSize(maxByteSize) {
 }
 
 LayerCache::~LayerCache() {
@@ -110,6 +110,7 @@
         layer = new Layer;
         layer->blend = true;
         layer->empty = true;
+        layer->fbo = 0;
 
         glGenTextures(1, &layer->texture);
         glBindTexture(GL_TEXTURE_2D, layer->texture);
@@ -121,6 +122,14 @@
 
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+#if DEBUG_LAYERS
+        uint32_t size = mCache.size();
+        for (uint32_t i = 0; i < size; i++) {
+            LayerSize ls = mCache.getKeyAt(i);
+            LAYER_LOGD("  Layer size %dx%d", ls.width, ls.height);
+        }
+#endif
     }
 
     return layer;
@@ -133,9 +142,10 @@
         while (mSize + size > mMaxSize) {
             Layer* oldest = mCache.removeOldest();
             deleteLayer(oldest);
+            LAYER_LOGD("  Deleting layer %.2fx%.2f", oldest->layer.getWidth(),
+                    oldest->layer.getHeight());
         }
 
-        layerSize.id = mIdGenerator++;
         mCache.put(layerSize, layer);
         mSize += size;
 
diff --git a/libs/hwui/LayerCache.h b/libs/hwui/LayerCache.h
index c0c7542..cbb7ae2 100644
--- a/libs/hwui/LayerCache.h
+++ b/libs/hwui/LayerCache.h
@@ -64,6 +64,7 @@
      * @param size The dimensions of the desired layer
      */
     Layer* get(LayerSize& size);
+
     /**
      * Adds the layer to the cache. The layer will not be added if there is
      * not enough space available.
@@ -96,7 +97,6 @@
     void deleteLayer(Layer* layer);
 
     GenerationCache<LayerSize, Layer*> mCache;
-    uint32_t mIdGenerator;
 
     uint32_t mSize;
     uint32_t mMaxSize;
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index e3790f5..ee5fe22 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -145,6 +145,9 @@
 
     mWidth = width;
     mHeight = height;
+
+    mFirstSnapshot->height = height;
+    mFirstSnapshot->viewport.set(0, 0, width, height);
 }
 
 void OpenGLRenderer::prepare() {
@@ -185,7 +188,7 @@
 }
 
 void OpenGLRenderer::releaseContext() {
-    glViewport(0, 0, mWidth, mHeight);
+    glViewport(0, 0, mSnapshot->viewport.getWidth(), mSnapshot->viewport.getHeight());
 
     glEnable(GL_SCISSOR_TEST);
     setScissorFromClip();
@@ -237,10 +240,17 @@
 bool OpenGLRenderer::restoreSnapshot() {
     bool restoreClip = mSnapshot->flags & Snapshot::kFlagClipSet;
     bool restoreLayer = mSnapshot->flags & Snapshot::kFlagIsLayer;
+    bool restoreOrtho = mSnapshot->flags & Snapshot::kFlagDirtyOrtho;
 
     sp<Snapshot> current = mSnapshot;
     sp<Snapshot> previous = mSnapshot->previous;
 
+    if (restoreOrtho) {
+        Rect& r = previous->viewport;
+        glViewport(r.left, r.top, r.right, r.bottom);
+        mOrthoMatrix.load(current->orthoMatrix);
+    }
+
     mSaveCount--;
     mSnapshot = previous;
 
@@ -261,7 +271,8 @@
 
 int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom,
         const SkPaint* p, int flags) {
-    int count = saveSnapshot(flags);
+    const GLuint previousFbo = mSnapshot->fbo;
+    const int count = saveSnapshot(flags);
 
     int alpha = 255;
     SkXfermode::Mode mode;
@@ -281,7 +292,7 @@
         mode = SkXfermode::kSrcOver_Mode;
     }
 
-    createLayer(mSnapshot, left, top, right, bottom, alpha, mode, flags);
+    createLayer(mSnapshot, left, top, right, bottom, alpha, mode, flags, previousFbo);
 
     return count;
 }
@@ -346,17 +357,21 @@
  *     something actually gets drawn are the layers regions cleared.
  */
 bool OpenGLRenderer::createLayer(sp<Snapshot> snapshot, float left, float top,
-        float right, float bottom, int alpha, SkXfermode::Mode mode,int flags) {
-    LAYER_LOGD("Requesting layer %fx%f", right - left, bottom - top);
+        float right, float bottom, int alpha, SkXfermode::Mode mode,
+        int flags, GLuint previousFbo) {
+    LAYER_LOGD("Requesting layer %.2fx%.2f", right - left, bottom - top);
     LAYER_LOGD("Layer cache size = %d", mCaches.layerCache.getSize());
 
+    const bool fboLayer = flags & SkCanvas::kClipToLayer_SaveFlag;
+
     // Window coordinates of the layer
     Rect bounds(left, top, right, bottom);
-    mSnapshot->transform->mapRect(bounds);
-
-    // Layers only make sense if they are in the framebuffer's bounds
-    bounds.intersect(*mSnapshot->clipRect);
-    bounds.snapToPixelBoundaries();
+    if (!fboLayer) {
+        mSnapshot->transform->mapRect(bounds);
+        // Layers only make sense if they are in the framebuffer's bounds
+        bounds.intersect(*mSnapshot->clipRect);
+        bounds.snapToPixelBoundaries();
+    }
 
     if (bounds.isEmpty() || bounds.getWidth() > mMaxTextureSize ||
             bounds.getHeight() > mMaxTextureSize) {
@@ -379,29 +394,77 @@
     snapshot->flags |= Snapshot::kFlagIsLayer;
     snapshot->layer = layer;
 
-    // Copy the framebuffer into the layer
-    glBindTexture(GL_TEXTURE_2D, layer->texture);
+    if (fboLayer) {
+        layer->fbo = mCaches.fboCache.get();
 
-    // TODO: Workaround for b/3054204
-    glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
-            bounds.getWidth(), bounds.getHeight(), 0);
+        snapshot->flags |= Snapshot::kFlagIsFboLayer;
+        snapshot->fbo = layer->fbo;
+        snapshot->resetTransform(-bounds.left, -bounds.top, 0.0f);
+        snapshot->resetClip(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight());
+        snapshot->viewport.set(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight());
+        snapshot->height = bounds.getHeight();
+        snapshot->flags |= Snapshot::kFlagDirtyOrtho;
+        snapshot->orthoMatrix.load(mOrthoMatrix);
 
-    // TODO: Waiting for b/3054204 to be fixed
-//    if (layer->empty) {
-//        glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
-//                bounds.getWidth(), bounds.getHeight(), 0);
-//        layer->empty = false;
-//    } else {
-//        glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bounds.left, mHeight - bounds.bottom,
-//                bounds.getWidth(), bounds.getHeight());
-//    }
-
-    if (flags & SkCanvas::kClipToLayer_SaveFlag && mSnapshot->clipTransformed(bounds)) {
         setScissorFromClip();
-    }
 
-    // Enqueue the buffer coordinates to clear the corresponding region later
-    mLayers.push(new Rect(bounds));
+        // Bind texture to FBO
+        glBindFramebuffer(GL_FRAMEBUFFER, layer->fbo);
+        glBindTexture(GL_TEXTURE_2D, layer->texture);
+
+        // Initialize the texture if needed
+        if (layer->empty) {
+            layer->empty = false;
+            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width, size.height, 0,
+                    GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+        }
+
+        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+                layer->texture, 0);
+
+        GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+        if (status != GL_FRAMEBUFFER_COMPLETE) {
+            LOGE("Framebuffer incomplete (GL error code 0x%x)", status);
+
+            glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+            glDeleteTextures(1, &layer->texture);
+            mCaches.fboCache.put(layer->fbo);
+
+            delete layer;
+
+            return false;
+        }
+
+        // Clear the FBO
+        glDisable(GL_SCISSOR_TEST);
+        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+        glClear(GL_COLOR_BUFFER_BIT);
+        glEnable(GL_SCISSOR_TEST);
+
+        // Change the ortho projection
+        glViewport(0, 0, bounds.getWidth(), bounds.getHeight());
+        mOrthoMatrix.loadOrtho(0.0f, bounds.getWidth(), bounds.getHeight(), 0.0f, -1.0f, 1.0f);
+    } else {
+        // Copy the framebuffer into the layer
+        glBindTexture(GL_TEXTURE_2D, layer->texture);
+
+        // TODO: Workaround for b/3054204
+        glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
+                bounds.getWidth(), bounds.getHeight(), 0);
+
+        // TODO: Waiting for b/3054204 to be fixed
+        // if (layer->empty) {
+        //     glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
+        //             bounds.getWidth(), bounds.getHeight(), 0);
+        //     layer->empty = false;
+        // } else {
+        //     glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bounds.left, mHeight - bounds.bottom,
+        //             bounds.getWidth(), bounds.getHeight());
+        //  }
+
+        // Enqueue the buffer coordinates to clear the corresponding region later
+        mLayers.push(new Rect(bounds));
+    }
 
     return true;
 }
@@ -415,14 +478,21 @@
         return;
     }
 
+    const bool fboLayer = current->flags & SkCanvas::kClipToLayer_SaveFlag;
+
+    if (fboLayer) {
+        // Unbind current FBO and restore previous one
+        glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo);
+    }
+
     // Restore the clip from the previous snapshot
     const Rect& clip = *previous->clipRect;
-    glScissor(clip.left, mHeight - clip.bottom, clip.getWidth(), clip.getHeight());
+    glScissor(clip.left, previous->height - clip.bottom, clip.getWidth(), clip.getHeight());
 
     Layer* layer = current->layer;
     const Rect& rect = layer->layer;
 
-    if (layer->alpha < 255) {
+    if (!fboLayer && layer->alpha < 255) {
         drawColorRect(rect.left, rect.top, rect.right, rect.bottom,
                 layer->alpha << 24, SkXfermode::kDstIn_Mode, true);
     }
@@ -430,20 +500,32 @@
     // Layers are already drawn with a top-left origin, don't flip the texture
     resetDrawTextureTexCoords(0.0f, 1.0f, 1.0f, 0.0f);
 
-    drawTextureMesh(rect.left, rect.top, rect.right, rect.bottom, layer->texture,
-            1.0f, layer->mode, layer->blend, &mMeshVertices[0].position[0],
-            &mMeshVertices[0].texture[0], GL_TRIANGLE_STRIP, gMeshCount, true, true);
+    if (fboLayer) {
+        drawTextureRect(rect.left, rect.top, rect.right, rect.bottom,
+                layer->texture, layer->alpha / 255.0f, layer->mode, layer->blend);
+    } else {
+        drawTextureMesh(rect.left, rect.top, rect.right, rect.bottom, layer->texture,
+                1.0f, layer->mode, layer->blend, &mMeshVertices[0].position[0],
+                &mMeshVertices[0].texture[0], GL_TRIANGLE_STRIP, gMeshCount, true, true);
+    }
 
     resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
 
+    if (fboLayer) {
+        // Detach the texture from the FBO
+        glBindFramebuffer(GL_FRAMEBUFFER, current->fbo);
+        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+        glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo);
+
+        // Put the FBO name back in the cache, if it doesn't fit, it will be destroyed
+        mCaches.fboCache.put(current->fbo);
+    }
+
     LayerSize size(rect.getWidth(), rect.getHeight());
-    // Failing to add the layer to the cache should happen only if the
-    // layer is too large
+    // Failing to add the layer to the cache should happen only if the layer is too large
     if (!mCaches.layerCache.put(size, layer)) {
         LAYER_LOGD("Deleting layer");
-
         glDeleteTextures(1, &layer->texture);
-
         delete layer;
     }
 }
@@ -503,7 +585,7 @@
 
 void OpenGLRenderer::setScissorFromClip() {
     const Rect& clip = *mSnapshot->clipRect;
-    glScissor(clip.left, mHeight - clip.bottom, clip.getWidth(), clip.getHeight());
+    glScissor(clip.left, mSnapshot->height - clip.bottom, clip.getWidth(), clip.getHeight());
 }
 
 const Rect& OpenGLRenderer::getClipBounds() {
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 1974cf0..e3d4653 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -167,11 +167,12 @@
      * @param alpha The translucency of the layer
      * @param mode The blending mode of the layer
      * @param flags The layer save flags
+     * @param previousFbo The name of the current framebuffer
      *
      * @return True if the layer was successfully created, false otherwise
      */
     bool createLayer(sp<Snapshot> snapshot, float left, float top, float right, float bottom,
-            int alpha, SkXfermode::Mode mode, int flags);
+            int alpha, SkXfermode::Mode mode, int flags, GLuint previousFbo);
 
     /**
      * Clears all the regions corresponding to the current list of layers.
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index d573805..3012824 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -45,7 +45,7 @@
 #define MB(s) s * 1024 * 1024
 
 #define DEFAULT_TEXTURE_CACHE_SIZE 18.0f
-#define DEFAULT_LAYER_CACHE_SIZE 4.0f
+#define DEFAULT_LAYER_CACHE_SIZE 8.0f
 #define DEFAULT_PATH_CACHE_SIZE 5.0f
 #define DEFAULT_PATCH_CACHE_SIZE 100
 #define DEFAULT_GRADIENT_CACHE_SIZE 0.5f
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index c736a1c..3d74b4c 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -43,7 +43,7 @@
  */
 class Snapshot: public LightRefBase<Snapshot> {
 public:
-    Snapshot(): flags(0), previous(NULL), layer(NULL) {
+    Snapshot(): flags(0), previous(NULL), layer(NULL), fbo(0) {
         transform = &mTransformRoot;
         clipRect = &mClipRectRoot;
     }
@@ -53,7 +53,8 @@
      * the previous snapshot.
      */
     Snapshot(const sp<Snapshot>& s, int saveFlags):
-            flags(0), previous(s), layer(NULL) {
+            flags(0), previous(s), layer(NULL),
+            fbo(s->fbo), viewport(s->viewport), height(s->height) {
         if (saveFlags & SkCanvas::kMatrix_SaveFlag) {
             mTransformRoot.load(*s->transform);
             transform = &mTransformRoot;
@@ -91,9 +92,19 @@
          */
         kFlagIsLayer = 0x2,
         /**
+         * Indicates that this snapshot is a special type of layer
+         * backed by an FBO. This flag only makes sense when the
+         * flag kFlagIsLayer is also set.
+         */
+        kFlagIsFboLayer = 0x4,
+        /**
          * Indicates that the local clip should be recomputed.
          */
-        kFlagDirtyLocalClip = 0x4,
+        kFlagDirtyLocalClip = 0x8,
+        /**
+         * Indicates that this snapshot has changed the ortho matrix.
+         */
+        kFlagDirtyOrtho = 0x10,
     };
 
     /**
@@ -169,6 +180,17 @@
         return mLocalClip;
     }
 
+    void resetTransform(float x, float y, float z) {
+        transform = &mTransformRoot;
+        transform->loadTranslate(x, y, z);
+    }
+
+    void resetClip(float left, float top, float right, float bottom) {
+        clipRect = &mClipRectRoot;
+        clipRect->set(left, top, right, bottom);
+        flags |= Snapshot::kFlagClipSet | Snapshot::kFlagDirtyLocalClip;
+    }
+
     /**
      * Dirty flags.
      */
@@ -185,6 +207,26 @@
     Layer* layer;
 
     /**
+     * Only set when the flag kFlagIsFboLayer is set.
+     */
+    GLuint fbo;
+
+    /**
+     * Current viewport.
+     */
+    Rect viewport;
+
+    /**
+     * Height of the framebuffer the snapshot is rendering into.
+     */
+    int height;
+
+    /**
+     * Contains the previous ortho matrix.
+     */
+    mat4 orthoMatrix;
+
+    /**
      * Local transformation. Holds the current translation, scale and
      * rotation values.
      */
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index adf6ee2..701df83 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -18,6 +18,8 @@
 
 #include <GLES2/gl2.h>
 
+#include <SkCanvas.h>
+
 #include <utils/threads.h>
 
 #include "TextureCache.h"
@@ -192,8 +194,12 @@
         // decoding happened
         texture->blend = !bitmap->isOpaque();
         break;
+    case SkBitmap::kIndex8_Config:
+        uploadPalettedTexture(resize, bitmap, texture->width, texture->height);
+        texture->blend = false;
+        break;
     default:
-        LOGW("Unsupported bitmap config");
+        LOGW("Unsupported bitmap config: %d", bitmap->getConfig());
         break;
     }
 
@@ -204,6 +210,20 @@
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 }
 
+void TextureCache::uploadPalettedTexture(bool resize, SkBitmap* bitmap,
+        uint32_t width, uint32_t height) {
+    SkBitmap rgbaBitmap;
+    rgbaBitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height);
+    rgbaBitmap.allocPixels();
+    rgbaBitmap.eraseColor(0);
+
+    SkCanvas canvas(rgbaBitmap);
+    canvas.drawBitmap(*bitmap, 0.0f, 0.0f, NULL);
+
+    uploadToTexture(resize, GL_RGBA, rgbaBitmap.rowBytesAsPixels(), height,
+            GL_UNSIGNED_BYTE, rgbaBitmap.getPixels());
+}
+
 void TextureCache::uploadToTexture(bool resize, GLenum format, GLsizei width, GLsizei height,
         GLenum type, const GLvoid * data) {
     if (resize) {
diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h
index 7cf66d9..34c5455 100644
--- a/libs/hwui/TextureCache.h
+++ b/libs/hwui/TextureCache.h
@@ -79,6 +79,7 @@
      */
     void generateTexture(SkBitmap* bitmap, Texture* texture, bool regenerate = false);
 
+    void uploadPalettedTexture(bool resize, SkBitmap* bitmap, uint32_t width, uint32_t height);
     void uploadToTexture(bool resize, GLenum format, GLsizei width, GLsizei height,
             GLenum type, const GLvoid * data);
 
diff --git a/libs/rs/rs.spec b/libs/rs/rs.spec
index a4752f4..0da637e 100644
--- a/libs/rs/rs.spec
+++ b/libs/rs/rs.spec
@@ -280,6 +280,17 @@
 	ret const void*
 	}
 
+AllocationResize1D {
+	param RsAllocation va
+	param uint32_t dimX
+	}
+
+AllocationResize2D {
+	param RsAllocation va
+	param uint32_t dimX
+	param uint32_t dimY
+	}
+
 SamplerBegin {
 	}
 
diff --git a/libs/rs/rsAllocation.cpp b/libs/rs/rsAllocation.cpp
index 0356e4d..2e9e0b3 100644
--- a/libs/rs/rsAllocation.cpp
+++ b/libs/rs/rsAllocation.cpp
@@ -488,12 +488,13 @@
     }
 }
 
-void Allocation::incRefs(const void *ptr, size_t ct) const
+void Allocation::incRefs(const void *ptr, size_t ct, size_t startOff) const
 {
     const uint8_t *p = static_cast<const uint8_t *>(ptr);
     const Element *e = mType->getElement();
     uint32_t stride = e->getSizeBytes();
 
+    p += stride * startOff;
     while (ct > 0) {
         e->incRefs(p);
         ct --;
@@ -501,12 +502,13 @@
     }
 }
 
-void Allocation::decRefs(const void *ptr, size_t ct) const
+void Allocation::decRefs(const void *ptr, size_t ct, size_t startOff) const
 {
     const uint8_t *p = static_cast<const uint8_t *>(ptr);
     const Element *e = mType->getElement();
     uint32_t stride = e->getSizeBytes();
 
+    p += stride * startOff;
     while (ct > 0) {
         e->decRefs(p);
         ct --;
@@ -514,6 +516,37 @@
     }
 }
 
+void Allocation::copyRange1D(Context *rsc, const Allocation *src, int32_t srcOff, int32_t destOff, int32_t len)
+{
+}
+
+void Allocation::resize1D(Context *rsc, uint32_t dimX)
+{
+    Type *t = mType->cloneAndResize1D(rsc, dimX);
+
+    uint32_t oldDimX = mType->getDimX();
+    if (dimX == oldDimX) {
+        return;
+    }
+
+    if (dimX < oldDimX) {
+        decRefs(mPtr, oldDimX - dimX, dimX);
+    }
+    mPtr = realloc(mPtr, t->getSizeBytes());
+
+    if (dimX > oldDimX) {
+        const Element *e = mType->getElement();
+        uint32_t stride = e->getSizeBytes();
+        memset(((uint8_t *)mPtr) + stride * oldDimX, 0, stride * (dimX - oldDimX));
+    }
+    mType.set(t);
+}
+
+void Allocation::resize2D(Context *rsc, uint32_t dimX, uint32_t dimY)
+{
+    LOGE("not implemented");
+}
+
 /////////////////
 //
 
@@ -822,6 +855,18 @@
     a->read(data);
 }
 
+void rsi_AllocationResize1D(Context *rsc, RsAllocation va, uint32_t dimX)
+{
+    Allocation *a = static_cast<Allocation *>(va);
+    a->resize1D(rsc, dimX);
+}
+
+void rsi_AllocationResize2D(Context *rsc, RsAllocation va, uint32_t dimX, uint32_t dimY)
+{
+    Allocation *a = static_cast<Allocation *>(va);
+    a->resize2D(rsc, dimX, dimY);
+}
+
 const void* rsi_AllocationGetType(Context *rsc, RsAllocation va)
 {
     Allocation *a = static_cast<Allocation *>(va);
diff --git a/libs/rs/rsAllocation.h b/libs/rs/rsAllocation.h
index ce5372f..24e245f 100644
--- a/libs/rs/rsAllocation.h
+++ b/libs/rs/rsAllocation.h
@@ -55,6 +55,10 @@
     void uploadToBufferObject(const Context *rsc);
     uint32_t getBufferObjectID() const {return mBufferID;}
 
+    void copyRange1D(Context *rsc, const Allocation *src, int32_t srcOff, int32_t destOff, int32_t len);
+
+    void resize1D(Context *rsc, uint32_t dimX);
+    void resize2D(Context *rsc, uint32_t dimX, uint32_t dimY);
 
     void data(Context *rsc, const void *data, uint32_t sizeBytes);
     void subData(Context *rsc, uint32_t xoff, uint32_t count, const void *data, uint32_t sizeBytes);
@@ -86,8 +90,8 @@
     bool getIsTexture() const {return mIsTexture;}
     bool getIsBufferObject() const {return mIsVertexBuffer;}
 
-    void incRefs(const void *ptr, size_t ct) const;
-    void decRefs(const void *ptr, size_t ct) const;
+    void incRefs(const void *ptr, size_t ct, size_t startOff = 0) const;
+    void decRefs(const void *ptr, size_t ct, size_t startOff = 0) const;
 
     void sendDirty() const;
     bool getHasGraphicsMipmaps() const {return mTextureGenMipmap;}
diff --git a/libs/rs/rsType.cpp b/libs/rs/rsType.cpp
index fc037a3..8cdb48a 100644
--- a/libs/rs/rsType.cpp
+++ b/libs/rs/rsType.cpp
@@ -274,6 +274,59 @@
     return false;
 }
 
+Type * Type::cloneAndResize1D(Context *rsc, uint32_t dimX) const
+{
+    TypeState * stc = &rsc->mStateType;
+    for (uint32_t ct=0; ct < stc->mTypes.size(); ct++) {
+        Type *t = stc->mTypes[ct];
+        if (t->getElement() != mElement.get()) continue;
+        if (t->getDimX() != dimX) continue;
+        if (t->getDimY() != mDimY) continue;
+        if (t->getDimZ() != mDimZ) continue;
+        if (t->getDimLOD() != mDimLOD) continue;
+        if (t->getDimFaces() != mFaces) continue;
+        t->incUserRef();
+        return t;
+    }
+
+    Type *nt = new Type(rsc);
+    nt->mElement.set(mElement);
+    nt->mDimX = dimX;
+    nt->mDimY = mDimY;
+    nt->mDimZ = mDimZ;
+    nt->mDimLOD = mDimLOD;
+    nt->mFaces = mFaces;
+    nt->compute();
+    return nt;
+}
+
+Type * Type::cloneAndResize2D(Context *rsc, uint32_t dimX, uint32_t dimY) const
+{
+    TypeState * stc = &rsc->mStateType;
+    for (uint32_t ct=0; ct < stc->mTypes.size(); ct++) {
+        Type *t = stc->mTypes[ct];
+        if (t->getElement() != mElement.get()) continue;
+        if (t->getDimX() != dimX) continue;
+        if (t->getDimY() != dimY) continue;
+        if (t->getDimZ() != mDimZ) continue;
+        if (t->getDimLOD() != mDimLOD) continue;
+        if (t->getDimFaces() != mFaces) continue;
+        t->incUserRef();
+        return t;
+    }
+
+    Type *nt = new Type(rsc);
+    nt->mElement.set(mElement);
+    nt->mDimX = dimX;
+    nt->mDimY = dimY;
+    nt->mDimZ = mDimZ;
+    nt->mDimLOD = mDimLOD;
+    nt->mFaces = mFaces;
+    nt->compute();
+    return nt;
+}
+
+
 //////////////////////////////////////////////////
 //
 namespace android {
diff --git a/libs/rs/rsType.h b/libs/rs/rsType.h
index 33faa87..b5548c0 100644
--- a/libs/rs/rsType.h
+++ b/libs/rs/rsType.h
@@ -79,6 +79,9 @@
 
     bool isEqual(const Type *other) const;
 
+    Type * cloneAndResize1D(Context *rsc, uint32_t dimX) const;
+    Type * cloneAndResize2D(Context *rsc, uint32_t dimX, uint32_t dimY) const;
+
 protected:
     struct LOD {
         size_t mX;
diff --git a/libs/utils/ZipFileRO.cpp b/libs/utils/ZipFileRO.cpp
index bee86b2..9b1f82f 100644
--- a/libs/utils/ZipFileRO.cpp
+++ b/libs/utils/ZipFileRO.cpp
@@ -508,6 +508,36 @@
 
         unsigned char lfhBuf[kLFHLen];
 
+#ifdef HAVE_PREAD
+        /*
+         * This file descriptor might be from zygote's preloaded assets,
+         * so we need to do an pread() instead of a lseek() + read() to
+         * guarantee atomicity across the processes with the shared file
+         * descriptors.
+         */
+        ssize_t actual =
+                TEMP_FAILURE_RETRY(pread(mFd, lfhBuf, sizeof(lfhBuf), localHdrOffset));
+
+        if (actual != sizeof(lfhBuf)) {
+            LOGW("failed reading lfh from offset %ld\n", localHdrOffset);
+            return false;
+        }
+
+        if (get4LE(lfhBuf) != kLFHSignature) {
+            LOGW("didn't find signature at start of lfh; wanted: offset=%ld data=0x%08x; "
+                    "got: data=0x%08lx\n",
+                    localHdrOffset, kLFHSignature, get4LE(lfhBuf));
+            return false;
+        }
+#else /* HAVE_PREAD */
+        /*
+         * For hosts don't have pread() we cannot guarantee atomic reads from
+         * an offset in a file. Android should never run on those platforms.
+         * File descriptors inherited from a fork() share file offsets and
+         * there would be nothing to protect from two different processes
+         * calling lseek() concurrently.
+         */
+
         {
             AutoMutex _l(mFdLock);
 
@@ -517,7 +547,7 @@
             }
 
             ssize_t actual =
-                TEMP_FAILURE_RETRY(read(mFd, lfhBuf, sizeof(lfhBuf)));
+                    TEMP_FAILURE_RETRY(read(mFd, lfhBuf, sizeof(lfhBuf)));
             if (actual != sizeof(lfhBuf)) {
                 LOGW("failed reading lfh from offset %ld\n", localHdrOffset);
                 return false;
@@ -531,6 +561,7 @@
                 return false;
             }
         }
+#endif /* HAVE_PREAD */
 
         off_t dataOffset = localHdrOffset + kLFHLen
             + get2LE(lfhBuf + kLFHNameLen) + get2LE(lfhBuf + kLFHExtraLen);
diff --git a/media/java/android/media/videoeditor/AudioTrack.java b/media/java/android/media/videoeditor/AudioTrack.java
index 8da7eaa..3ebad00 100755
--- a/media/java/android/media/videoeditor/AudioTrack.java
+++ b/media/java/android/media/videoeditor/AudioTrack.java
@@ -183,11 +183,13 @@
      */

     @SuppressWarnings("unused")

     private AudioTrack() throws IOException {

-        this(null, null);

+        this(null, null, null);

     }

 

     /**

      * Constructor

+     *

+     * @param editor The video editor reference

      * @param audioTrackId The audio track id

      * @param filename The absolute file name

      *

@@ -195,7 +197,8 @@
      * @throws IllegalArgumentException if file format is not supported or if

      *             the codec is not supported

      */

-    public AudioTrack(String audioTrackId, String filename) throws IOException {

+    public AudioTrack(VideoEditor editor, String audioTrackId, String filename)

+            throws IOException {

         mUniqueId = audioTrackId;

         mFilename = filename;

         mStartTimeMs = 0;

@@ -233,6 +236,7 @@
     /**

      * Constructor

      *

+     * @param editor The video editor reference

      * @param audioTrackId The audio track id

      * @param filename The audio filename

      * @param startTimeMs the start time in milliseconds (relative to the

@@ -248,8 +252,9 @@
      *

      * @throws IOException if file is not found

      */

-    AudioTrack(String audioTrackId, String filename, long startTimeMs, long beginMs, long endMs,

-            boolean loop, int volume, boolean muted, String audioWaveformFilename) throws IOException {

+    AudioTrack(VideoEditor editor, String audioTrackId, String filename, long startTimeMs,

+            long beginMs, long endMs, boolean loop, int volume, boolean muted,

+            String audioWaveformFilename) throws IOException {

         mUniqueId = audioTrackId;

         mFilename = filename;

         mStartTimeMs = startTimeMs;

diff --git a/media/java/android/media/videoeditor/Effect.java b/media/java/android/media/videoeditor/Effect.java
index 038bfc2..f5e6a67 100755
--- a/media/java/android/media/videoeditor/Effect.java
+++ b/media/java/android/media/videoeditor/Effect.java
@@ -124,6 +124,23 @@
     }

 

     /**

+     * Set the start time and duration

+     *

+     * @param startTimeMs start time in milliseconds

+     * @param durationMs The duration in milliseconds

+     */

+    public void setStartTimeAndDuration(long startTimeMs, long durationMs) {

+        if (startTimeMs + durationMs > mMediaItem.getTimelineDuration()) {

+            throw new IllegalArgumentException("Invalid start time or duration");

+        }

+

+        mStartTimeMs = startTimeMs;

+        mDurationMs = durationMs;

+

+        mMediaItem.invalidateTransitions(this);

+    }

+

+    /**

      * @return The media item owner

      */

     public MediaItem getMediaItem() {

diff --git a/media/java/android/media/videoeditor/MediaImageItem.java b/media/java/android/media/videoeditor/MediaImageItem.java
index ab23119..df3c5fb 100755
--- a/media/java/android/media/videoeditor/MediaImageItem.java
+++ b/media/java/android/media/videoeditor/MediaImageItem.java
@@ -17,7 +17,6 @@
 package android.media.videoeditor;

 

 import java.io.IOException;

-import java.util.List;

 

 import android.graphics.Bitmap;

 import android.graphics.BitmapFactory;

@@ -54,12 +53,13 @@
      */

     @SuppressWarnings("unused")

     private MediaImageItem() throws IOException {

-        this(null, null, 0, RENDERING_MODE_BLACK_BORDER);

+        this(null, null, null, 0, RENDERING_MODE_BLACK_BORDER);

     }

 

     /**

      * Constructor

      *

+     * @param editor The video editor reference

      * @param mediaItemId The media item id

      * @param filename The image file name

      * @param durationMs The duration of the image on the storyboard

@@ -67,9 +67,10 @@
      *

      * @throws IOException

      */

-    public MediaImageItem(String mediaItemId, String filename, long durationMs, int renderingMode)

+    public MediaImageItem(VideoEditor editor, String mediaItemId, String filename, long durationMs,

+            int renderingMode)

             throws IOException {

-        super(mediaItemId, filename, renderingMode);

+        super(editor, mediaItemId, filename, renderingMode);

 

         // Determine the dimensions of the image

         final BitmapFactory.Options dbo = new BitmapFactory.Options();

@@ -153,55 +154,16 @@
     }

 

     /**

-     * This method will adjust the duration of bounding transitions if the

-     * current duration of the transactions become greater than the maximum

-     * allowable duration.

+     * This method will adjust the duration of bounding transitions, effects

+     * and overlays if the current duration of the transactions become greater

+     * than the maximum allowable duration.

      *

      * @param durationMs The duration of the image in the storyboard timeline

      */

     public void setDuration(long durationMs) {

         mDurationMs = durationMs;

 

-        // Check if the duration of transitions need to be adjusted

-        if (mBeginTransition != null) {

-            final long maxDurationMs = mBeginTransition.getMaximumDuration();

-            if (mBeginTransition.getDuration() > maxDurationMs) {

-                mBeginTransition.setDuration(maxDurationMs);

-            }

-        }

-

-        if (mEndTransition != null) {

-            final long maxDurationMs = mEndTransition.getMaximumDuration();

-            if (mEndTransition.getDuration() > maxDurationMs) {

-                mEndTransition.setDuration(maxDurationMs);

-            }

-        }

-

-        final List<Overlay> overlays = getAllOverlays();

-        for (Overlay overlay : overlays) {

-            // Adjust the start time if necessary

-            if (overlay.getStartTime() < getTimelineDuration()) {

-                overlay.setStartTime(0);

-            }

-

-            // Adjust the duration if necessary

-            if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration()) {

-                overlay.setDuration(getTimelineDuration() - overlay.getStartTime());

-            }

-        }

-

-        final List<Effect> effects = getAllEffects();

-        for (Effect effect : effects) {

-            // Adjust the start time if necessary

-            if (effect.getStartTime() < getTimelineDuration()) {

-                effect.setStartTime(0);

-            }

-

-            // Adjust the duration if necessary

-            if (effect.getStartTime() + effect.getDuration() > getTimelineDuration()) {

-                effect.setDuration(getTimelineDuration() - effect.getStartTime());

-            }

-        }

+        adjustElementsDuration();

     }

 

     /*

diff --git a/media/java/android/media/videoeditor/MediaItem.java b/media/java/android/media/videoeditor/MediaItem.java
index f4651af..d9c38af 100755
--- a/media/java/android/media/videoeditor/MediaItem.java
+++ b/media/java/android/media/videoeditor/MediaItem.java
@@ -70,6 +70,7 @@
     /**

      * Constructor

      *

+     * @param editor The video editor reference

      * @param mediaItemId The MediaItem id

      * @param filename name of the media file.

      * @param renderingMode The rendering mode

@@ -79,7 +80,8 @@
      *             supported the exception object contains the unsupported

      *             capability

      */

-    protected MediaItem(String mediaItemId, String filename, int renderingMode) throws IOException {

+    protected MediaItem(VideoEditor editor, String mediaItemId, String filename,

+            int renderingMode) throws IOException {

         mUniqueId = mediaItemId;

         mFilename = filename;

         mRenderingMode = renderingMode;

@@ -472,4 +474,73 @@
             }

         }

     }

+

+    /**

+     * Adjust the duration of effects, overlays and transitions.

+     * This method will be called after a media item duration is changed.

+     */

+    protected void adjustElementsDuration() {

+        // Check if the duration of transitions need to be adjusted

+        if (mBeginTransition != null) {

+            final long maxDurationMs = mBeginTransition.getMaximumDuration();

+            if (mBeginTransition.getDuration() > maxDurationMs) {

+                mBeginTransition.setDuration(maxDurationMs);

+            }

+        }

+

+        if (mEndTransition != null) {

+            final long maxDurationMs = mEndTransition.getMaximumDuration();

+            if (mEndTransition.getDuration() > maxDurationMs) {

+                mEndTransition.setDuration(maxDurationMs);

+            }

+        }

+

+        final List<Overlay> overlays = getAllOverlays();

+        for (Overlay overlay : overlays) {

+            // Adjust the start time if necessary

+            final long overlayStartTimeMs;

+            if (overlay.getStartTime() > getTimelineDuration()) {

+                overlayStartTimeMs = 0;

+            } else {

+                overlayStartTimeMs = overlay.getStartTime();

+            }

+

+            // Adjust the duration if necessary

+            final long overlayDurationMs;

+            if (overlayStartTimeMs + overlay.getDuration() > getTimelineDuration()) {

+                overlayDurationMs = getTimelineDuration() - overlayStartTimeMs;

+            } else {

+                overlayDurationMs = overlay.getDuration();

+            }

+

+            if (overlayStartTimeMs != overlay.getStartTime() ||

+                    overlayDurationMs != overlay.getDuration()) {

+                overlay.setStartTimeAndDuration(overlayStartTimeMs, overlayDurationMs);

+            }

+        }

+

+        final List<Effect> effects = getAllEffects();

+        for (Effect effect : effects) {

+            // Adjust the start time if necessary

+            final long effectStartTimeMs;

+            if (effect.getStartTime() > getTimelineDuration()) {

+                effectStartTimeMs = 0;

+            } else {

+                effectStartTimeMs = effect.getStartTime();

+            }

+

+            // Adjust the duration if necessary

+            final long effectDurationMs;

+            if (effectStartTimeMs + effect.getDuration() > getTimelineDuration()) {

+                effectDurationMs = getTimelineDuration() - effectStartTimeMs;

+            } else {

+                effectDurationMs = effect.getDuration();

+            }

+

+            if (effectStartTimeMs != effect.getStartTime() ||

+                    effectDurationMs != effect.getDuration()) {

+                effect.setStartTimeAndDuration(effectStartTimeMs, effectDurationMs);

+            }

+        }

+    }

 }

diff --git a/media/java/android/media/videoeditor/MediaVideoItem.java b/media/java/android/media/videoeditor/MediaVideoItem.java
index cc0a155..dd12336 100755
--- a/media/java/android/media/videoeditor/MediaVideoItem.java
+++ b/media/java/android/media/videoeditor/MediaVideoItem.java
@@ -17,7 +17,6 @@
 package android.media.videoeditor;

 

 import java.io.IOException;

-import java.util.List;

 

 import android.graphics.Bitmap;

 import android.util.Log;

@@ -193,26 +192,29 @@
      */

     @SuppressWarnings("unused")

     private MediaVideoItem() throws IOException {

-        this(null, null, RENDERING_MODE_BLACK_BORDER);

+        this(null, null, null, RENDERING_MODE_BLACK_BORDER);

     }

 

     /**

      * Constructor

      *

+     * @param editor The video editor reference

      * @param mediaItemId The MediaItem id

      * @param filename The image file name

      * @param renderingMode The rendering mode

      *

      * @throws IOException if the file cannot be opened for reading

      */

-    public MediaVideoItem(String mediaItemId, String filename, int renderingMode)

+    public MediaVideoItem(VideoEditor editor, String mediaItemId, String filename,

+            int renderingMode)

         throws IOException {

-        this(mediaItemId, filename, renderingMode, 0, END_OF_FILE, 100, false, null);

+        this(editor, mediaItemId, filename, renderingMode, 0, END_OF_FILE, 100, false, null);

     }

 

     /**

      * Constructor

      *

+     * @param editor The video editor reference

      * @param mediaItemId The MediaItem id

      * @param filename The image file name

      * @param renderingMode The rendering mode

@@ -227,10 +229,10 @@
      *

      * @throws IOException if the file cannot be opened for reading

      */

-    MediaVideoItem(String mediaItemId, String filename, int renderingMode,

+    MediaVideoItem(VideoEditor editor, String mediaItemId, String filename, int renderingMode,

             long beginMs, long endMs, int volumePercent, boolean muted,

             String audioWaveformFilename)  throws IOException {

-        super(mediaItemId, filename, renderingMode);

+        super(editor, mediaItemId, filename, renderingMode);

         // TODO: Set these variables correctly

         mWidth = 1080;

         mHeight = 720;

@@ -257,9 +259,9 @@
 

     /**

      * Sets the start and end marks for trimming a video media item.

-     * This method will adjust the duration of bounding transitions if the

-     * current duration of the transactions become greater than the maximum

-     * allowable duration.

+     * This method will adjust the duration of bounding transitions, effects

+     * and overlays if the current duration of the transactions become greater

+     * than the maximum allowable duration.

      *

      * @param beginMs Start time in milliseconds. Set to 0 to extract from the

      *           beginning

@@ -293,46 +295,7 @@
         mBeginBoundaryTimeMs = beginMs;

         mEndBoundaryTimeMs = endMs;

 

-        // Check if the duration of transitions need to be adjusted

-        if (mBeginTransition != null) {

-            final long maxDurationMs = mBeginTransition.getMaximumDuration();

-            if (mBeginTransition.getDuration() > maxDurationMs) {

-                mBeginTransition.setDuration(maxDurationMs);

-            }

-        }

-

-        if (mEndTransition != null) {

-            final long maxDurationMs = mEndTransition.getMaximumDuration();

-            if (mEndTransition.getDuration() > maxDurationMs) {

-                mEndTransition.setDuration(maxDurationMs);

-            }

-        }

-

-        final List<Overlay> overlays = getAllOverlays();

-        for (Overlay overlay : overlays) {

-            // Adjust the start time if necessary

-            if (overlay.getStartTime() < mBeginBoundaryTimeMs) {

-                overlay.setStartTime(mBeginBoundaryTimeMs);

-            }

-

-            // Adjust the duration if necessary

-            if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration()) {

-                overlay.setDuration(getTimelineDuration() - overlay.getStartTime());

-            }

-        }

-

-        final List<Effect> effects = getAllEffects();

-        for (Effect effect : effects) {

-            // Adjust the start time if necessary

-            if (effect.getStartTime() < mBeginBoundaryTimeMs) {

-                effect.setStartTime(mBeginBoundaryTimeMs);

-            }

-

-            // Adjust the duration if necessary

-            if (effect.getStartTime() + effect.getDuration() > getTimelineDuration()) {

-                effect.setDuration(getTimelineDuration() - effect.getStartTime());

-            }

-        }

+        adjustElementsDuration();

     }

 

     /**

diff --git a/media/java/android/media/videoeditor/Overlay.java b/media/java/android/media/videoeditor/Overlay.java
index d9e7f85..c58b5cb 100755
--- a/media/java/android/media/videoeditor/Overlay.java
+++ b/media/java/android/media/videoeditor/Overlay.java
@@ -126,6 +126,23 @@
     }

 

     /**

+     * Set the start time and duration

+     *

+     * @param startTimeMs start time in milliseconds

+     * @param durationMs The duration in milliseconds

+     */

+    public void setStartTimeAndDuration(long startTimeMs, long durationMs) {

+        if (startTimeMs + durationMs > mMediaItem.getTimelineDuration()) {

+            throw new IllegalArgumentException("Invalid start time or duration");

+        }

+

+        mStartTimeMs = startTimeMs;

+        mDurationMs = durationMs;

+

+        mMediaItem.invalidateTransitions(this);

+    }

+

+    /**

      * @return The media item owner

      */

     public MediaItem getMediaItem() {

diff --git a/media/java/android/media/videoeditor/VideoEditorTestImpl.java b/media/java/android/media/videoeditor/VideoEditorTestImpl.java
index b39d9d8..c3cb82a 100644
--- a/media/java/android/media/videoeditor/VideoEditorTestImpl.java
+++ b/media/java/android/media/videoeditor/VideoEditorTestImpl.java
@@ -765,7 +765,7 @@
                         if (MediaImageItem.class.getSimpleName().equals(type)) {
                             final long durationMs = Long.parseLong(parser.getAttributeValue("",
                                     ATTR_DURATION));
-                            currentMediaItem = new MediaImageItem(mediaItemId, filename,
+                            currentMediaItem = new MediaImageItem(this, mediaItemId, filename,
                                     durationMs, renderingMode);
                         } else if (MediaVideoItem.class.getSimpleName().equals(type)) {
                             final long beginMs = Long.parseLong(parser.getAttributeValue("",
@@ -778,7 +778,7 @@
                                     ATTR_MUTED));
                             final String audioWaveformFilename = parser.getAttributeValue("",
                                     ATTR_AUDIO_WAVEFORM_FILENAME);
-                            currentMediaItem = new MediaVideoItem(mediaItemId, filename,
+                            currentMediaItem = new MediaVideoItem(this, mediaItemId, filename,
                                     renderingMode, beginMs, endMs, volume, muted,
                                     audioWaveformFilename);
 
@@ -1017,7 +1017,7 @@
         final boolean loop = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_LOOP));
         final String waveformFilename = parser.getAttributeValue("", ATTR_AUDIO_WAVEFORM_FILENAME);
         try {
-            final AudioTrack audioTrack = new AudioTrack(audioTrackId, filename, startTimeMs,
+            final AudioTrack audioTrack = new AudioTrack(this, audioTrackId, filename, startTimeMs,
                     beginMs, endMs, loop, volume, muted, waveformFilename);
 
             return audioTrack;
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index bf0c6e17..7a78185 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -966,6 +966,65 @@
     }
 }
 
+/*
+ * Check to see whether the requested video width and height is one
+ * of the supported sizes. It returns true if so; otherwise, it
+ * returns false.
+ */
+bool StagefrightRecorder::isVideoSizeSupported(
+    const Vector<Size>& supportedSizes) const {
+
+    LOGV("isVideoSizeSupported");
+    for (size_t i = 0; i < supportedSizes.size(); ++i) {
+        if (mVideoWidth  == supportedSizes[i].width &&
+            mVideoHeight == supportedSizes[i].height) {
+            return true;
+        }
+    }
+    return false;
+}
+
+/*
+ * If the preview and video output is separate, we only set the
+ * the video size, and applications should set the preview size
+ * to some proper value, and the recording framework will not
+ * change the preview size; otherwise, if the video and preview
+ * output is the same, we need to set the preview to be the same
+ * as the requested video size.
+ *
+ * On return, it also returns whether the setVideoSize() is
+ * supported.
+ */
+status_t StagefrightRecorder::setCameraVideoSize(
+    CameraParameters* params,
+    bool* isSetVideoSizeSupported) {
+    LOGV("setCameraVideoSize: %dx%d", mVideoWidth, mVideoHeight);
+
+    // Check whether the requested video size is supported
+    Vector<Size> sizes;
+    params->getSupportedVideoSizes(sizes);
+    *isSetVideoSizeSupported = true;
+    if (sizes.size() == 0) {
+        LOGD("Camera does not support setVideoSize()");
+        params->getSupportedPreviewSizes(sizes);
+        *isSetVideoSizeSupported = false;
+    }
+    if (!isVideoSizeSupported(sizes)) {
+        LOGE("Camera does not support video size (%dx%d)!",
+            mVideoWidth, mVideoHeight);
+        return BAD_VALUE;
+    }
+
+    // Actually set the video size
+    if (isSetVideoSizeSupported) {
+        params->setVideoSize(mVideoWidth, mVideoHeight);
+    } else {
+        params->setPreviewSize(mVideoWidth, mVideoHeight);
+    }
+
+    return OK;
+}
+
 status_t StagefrightRecorder::setupCamera() {
     if (!mCaptureTimeLapse) {
         // Dont clip for time lapse capture as encoder will have enough
@@ -993,8 +1052,11 @@
     // dont change the preview size because time lapse may be using still camera
     // as mVideoWidth, mVideoHeight may correspond to HD resolution not
     // supported by the video camera.
+    bool isSetVideoSizeSupported = false;
     if (!mCaptureTimeLapse) {
-        params.setPreviewSize(mVideoWidth, mVideoHeight);
+        if (OK != setCameraVideoSize(&params, &isSetVideoSizeSupported)) {
+            return BAD_VALUE;
+        }
     }
 
     params.setPreviewFrameRate(mFrameRate);
@@ -1008,7 +1070,11 @@
 
     // Check on video frame size
     int frameWidth = 0, frameHeight = 0;
-    newCameraParams.getPreviewSize(&frameWidth, &frameHeight);
+    if (isSetVideoSizeSupported) {
+        newCameraParams.getVideoSize(&frameWidth, &frameHeight);
+    } else {
+        newCameraParams.getPreviewSize(&frameWidth, &frameHeight);
+    }
     if (!mCaptureTimeLapse &&
         (frameWidth  < 0 || frameWidth  != mVideoWidth ||
         frameHeight < 0 || frameHeight != mVideoHeight)) {
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index 02d9a01..f14c704 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -19,6 +19,7 @@
 #define STAGEFRIGHT_RECORDER_H_
 
 #include <media/MediaRecorderBase.h>
+#include <camera/CameraParameters.h>
 #include <utils/String8.h>
 
 namespace android {
@@ -125,6 +126,9 @@
     status_t startRTPRecording();
     sp<MediaSource> createAudioSource();
     status_t setupCamera();
+    bool     isVideoSizeSupported(const Vector<Size>& supportedSizes) const;
+    status_t setCameraVideoSize(CameraParameters* params,
+                bool *isSetVideoSizeSupported);
     status_t setupCameraSource(sp<CameraSource> *cameraSource);
     status_t setupAudioEncoder(const sp<MediaWriter>& writer);
     status_t setupVideoEncoder(
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index 546df47..90b1aab 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -40,6 +40,7 @@
 static const int64_t kMax32BitFileSize = 0x007fffffffLL;
 static const uint8_t kNalUnitTypeSeqParamSet = 0x07;
 static const uint8_t kNalUnitTypePicParamSet = 0x08;
+static const int64_t kVideoMediaTimeAdjustPeriodTimeUs = 10000000LL;  // 10s
 
 class MPEG4Writer::Track {
 public:
@@ -148,6 +149,28 @@
     int64_t mPreviousTrackTimeUs;
     int64_t mTrackEveryTimeDurationUs;
 
+    // Has the media time adjustment for video started?
+    bool    mIsMediaTimeAdjustmentOn;
+    // The time stamp when previous media time adjustment period starts
+    int64_t mPrevMediaTimeAdjustTimestampUs;
+    // Number of vidoe frames whose time stamp may be adjusted
+    int64_t mMediaTimeAdjustNumFrames;
+    // The sample number when previous meida time adjustmnet period starts
+    int64_t mPrevMediaTimeAdjustSample;
+    // The total accumulated drift time within a period of
+    // kVideoMediaTimeAdjustPeriodTimeUs.
+    int64_t mTotalDriftTimeToAdjustUs;
+    // The total accumalated drift time since the start of the recording
+    // excluding the current time adjustment period
+    int64_t mPrevTotalAccumDriftTimeUs;
+
+    // Update the audio track's drift information.
+    void updateDriftTime(const sp<MetaData>& meta);
+
+    // Adjust the time stamp of the video track according to
+    // the drift time information from the audio track.
+    void adjustMediaTime(int64_t *timestampUs);
+
     static void *ThreadWrapper(void *me);
     status_t threadEntry();
 
@@ -319,7 +342,7 @@
         size = MAX_MOOV_BOX_SIZE;
     }
 
-    LOGI("limits: %lld/%lld bytes/us, bit rate: %d bps and the estimated"
+    LOGV("limits: %lld/%lld bytes/us, bit rate: %d bps and the estimated"
          " moov size %lld bytes",
          mMaxFileSizeLimitBytes, mMaxFileDurationLimitUs, bitRate, size);
     return factor * size;
@@ -346,7 +369,7 @@
         // If file size is set to be larger than the 32 bit file
         // size limit, treat it as an error.
         if (mMaxFileSizeLimitBytes > kMax32BitFileSize) {
-            LOGW("32-bi file size limit (%lld bytes) too big. "
+            LOGW("32-bit file size limit (%lld bytes) too big. "
                  "It is changed to %lld bytes",
                 mMaxFileSizeLimitBytes, kMax32BitFileSize);
             mMaxFileSizeLimitBytes = kMax32BitFileSize;
@@ -1149,6 +1172,12 @@
     mNumStscTableEntries = 0;
     mNumSttsTableEntries = 0;
     mMdatSizeBytes = 0;
+    mIsMediaTimeAdjustmentOn = false;
+    mPrevMediaTimeAdjustTimestampUs = 0;
+    mMediaTimeAdjustNumFrames = 0;
+    mPrevMediaTimeAdjustSample = 0;
+    mTotalDriftTimeToAdjustUs = 0;
+    mPrevTotalAccumDriftTimeUs = 0;
 
     pthread_create(&mThread, &attr, ThreadWrapper, this);
     pthread_attr_destroy(&attr);
@@ -1437,6 +1466,145 @@
     return OK;
 }
 
+/*
+* The video track's media time adjustment for real-time applications
+* is described as follows:
+*
+* First, the media time adjustment is done for every period of
+* kVideoMediaTimeAdjustPeriodTimeUs. kVideoMediaTimeAdjustPeriodTimeUs
+* is currently a fixed value chosen heuristically. The value of
+* kVideoMediaTimeAdjustPeriodTimeUs should not be very large or very small
+* for two considerations: on one hand, a relatively large value
+* helps reduce large fluctuation of drift time in the audio encoding
+* path; while on the other hand, a relatively small value helps keep
+* restoring synchronization in audio/video more frequently. Note for the
+* very first period of kVideoMediaTimeAdjustPeriodTimeUs, there is
+* no media time adjustment for the video track.
+*
+* Second, the total accumulated audio track time drift found
+* in a period of kVideoMediaTimeAdjustPeriodTimeUs is distributed
+* over a stream of incoming video frames. The number of video frames
+* affected is determined based on the number of recorded video frames
+* within the past kVideoMediaTimeAdjustPeriodTimeUs period.
+* We choose to distribute the drift time over only a portion
+* (rather than all) of the total number of recorded video frames
+* in order to make sure that the video track media time adjustment is
+* completed for the current period before the next video track media
+* time adjustment period starts. Currently, the portion chosen is a
+* half (0.5).
+*
+* Last, various additional checks are performed to ensure that
+* the actual audio encoding path does not have too much drift.
+* In particular, 1) we want to limit the average incremental time
+* adjustment for each video frame to be less than a threshold
+* for a single period of kVideoMediaTimeAdjustPeriodTimeUs.
+* Currently, the threshold is set to 5 ms. If the average incremental
+* media time adjustment for a video frame is larger than the
+* threshold, the audio encoding path has too much time drift.
+* 2) We also want to limit the total time drift in the audio
+* encoding path to be less than a threshold for a period of
+* kVideoMediaTimeAdjustPeriodTimeUs. Currently, the threshold
+* is 0.5% of kVideoMediaTimeAdjustPeriodTimeUs. If the time drift of
+* the audio encoding path is larger than the threshold, the audio
+* encoding path has too much time drift. We treat the large time
+* drift of the audio encoding path as errors, since there is no
+* way to keep audio/video in synchronization for real-time
+* applications if the time drift is too large unless we drop some
+* video frames, which has its own problems that we don't want
+* to get into for the time being.
+*/
+void MPEG4Writer::Track::adjustMediaTime(int64_t *timestampUs) {
+    if (*timestampUs - mPrevMediaTimeAdjustTimestampUs >=
+        kVideoMediaTimeAdjustPeriodTimeUs) {
+
+        LOGV("New media time adjustment period at %lld us", *timestampUs);
+        mIsMediaTimeAdjustmentOn = true;
+        mMediaTimeAdjustNumFrames =
+                (mNumSamples - mPrevMediaTimeAdjustSample) >> 1;
+
+        mPrevMediaTimeAdjustTimestampUs = *timestampUs;
+        mPrevMediaTimeAdjustSample = mNumSamples;
+        int64_t totalAccumDriftTimeUs = mOwner->getDriftTimeUs();
+        mTotalDriftTimeToAdjustUs =
+                totalAccumDriftTimeUs - mPrevTotalAccumDriftTimeUs;
+
+        mPrevTotalAccumDriftTimeUs = totalAccumDriftTimeUs;
+
+        // Check on incremental adjusted time per frame
+        int64_t adjustTimePerFrameUs =
+                mTotalDriftTimeToAdjustUs / mMediaTimeAdjustNumFrames;
+
+        if (adjustTimePerFrameUs < 0) {
+            adjustTimePerFrameUs = -adjustTimePerFrameUs;
+        }
+        if (adjustTimePerFrameUs >= 5000) {
+            LOGE("Adjusted time per video frame is %lld us",
+                adjustTimePerFrameUs);
+            CHECK(!"Video frame time adjustment is too large!");
+        }
+
+        // Check on total accumulated time drift within a period of
+        // kVideoMediaTimeAdjustPeriodTimeUs.
+        int64_t driftPercentage = (mTotalDriftTimeToAdjustUs * 1000)
+                / kVideoMediaTimeAdjustPeriodTimeUs;
+
+        if (driftPercentage < 0) {
+            driftPercentage = -driftPercentage;
+        }
+        if (driftPercentage > 5) {
+            LOGE("Audio track has time drift %lld us over %lld us",
+                mTotalDriftTimeToAdjustUs,
+                kVideoMediaTimeAdjustPeriodTimeUs);
+
+            CHECK(!"The audio track media time drifts too much!");
+        }
+
+    }
+
+    if (mIsMediaTimeAdjustmentOn) {
+        if (mNumSamples - mPrevMediaTimeAdjustSample <=
+            mMediaTimeAdjustNumFrames) {
+
+            // Do media time incremental adjustment
+            int64_t incrementalAdjustTimeUs =
+                        (mTotalDriftTimeToAdjustUs *
+                            (mNumSamples - mPrevMediaTimeAdjustSample))
+                                / mMediaTimeAdjustNumFrames;
+
+            *timestampUs +=
+                (incrementalAdjustTimeUs + mPrevTotalAccumDriftTimeUs);
+
+            LOGV("Incremental video frame media time adjustment: %lld us",
+                (incrementalAdjustTimeUs + mPrevTotalAccumDriftTimeUs));
+        } else {
+            // Within the remaining adjustment period,
+            // no incremental adjustment is needed.
+            *timestampUs +=
+                (mTotalDriftTimeToAdjustUs + mPrevTotalAccumDriftTimeUs);
+
+            LOGV("Fixed video frame media time adjustment: %lld us",
+                (mTotalDriftTimeToAdjustUs + mPrevTotalAccumDriftTimeUs));
+        }
+    }
+}
+
+/*
+ * Updates the drift time from the audio track so that
+ * the video track can get the updated drift time information
+ * from the file writer. The fluctuation of the drift time of the audio
+ * encoding path is smoothed out with a simple filter by giving a larger
+ * weight to more recently drift time. The filter coefficients, 0.5 and 0.5,
+ * are heuristically determined.
+ */
+void MPEG4Writer::Track::updateDriftTime(const sp<MetaData>& meta) {
+    int64_t driftTimeUs = 0;
+    if (meta->findInt64(kKeyDriftTime, &driftTimeUs)) {
+        int64_t prevDriftTimeUs = mOwner->getDriftTimeUs();
+        int64_t timeUs = (driftTimeUs + prevDriftTimeUs) >> 1;
+        mOwner->setDriftTimeUs(timeUs);
+    }
+}
+
 status_t MPEG4Writer::Track::threadEntry() {
     int32_t count = 0;
     const int64_t interleaveDurationUs = mOwner->interleaveDuration();
@@ -1587,24 +1755,16 @@
 
         timestampUs -= previousPausedDurationUs;
         CHECK(timestampUs >= 0);
-        if (mIsRealTimeRecording && !mIsAudio) {
-            // The minor adjustment on the timestamp is heuristic/experimental
-            // We are adjusting the timestamp to reduce the fluctuation of the duration
-            // of neighboring samples. This in turn helps reduce the track header size,
-            // especially, the number of entries in the "stts" box.
-            if (mNumSamples > 1) {
-                int64_t currDriftTimeUs = mOwner->getDriftTimeUs();
-                int64_t durationUs = timestampUs + currDriftTimeUs - lastTimestampUs;
-                int64_t diffUs = (durationUs > lastDurationUs)
-                            ? durationUs - lastDurationUs
-                            : lastDurationUs - durationUs;
-                if (diffUs <= 5000) {  // XXX: Magic number 5ms
-                    timestampUs = lastTimestampUs + lastDurationUs;
-                } else {
-                    timestampUs += currDriftTimeUs;
-                }
+
+        // Media time adjustment for real-time applications
+        if (mIsRealTimeRecording) {
+            if (mIsAudio) {
+                updateDriftTime(meta_data);
+            } else {
+                adjustMediaTime(&timestampUs);
             }
         }
+
         CHECK(timestampUs >= 0);
         if (mNumSamples > 1) {
             if (timestampUs <= lastTimestampUs) {
@@ -1656,12 +1816,6 @@
         lastDurationUs = timestampUs - lastTimestampUs;
         lastDurationTicks = currDurationTicks;
         lastTimestampUs = timestampUs;
-        if (mIsRealTimeRecording && mIsAudio) {
-            int64_t driftTimeUs = 0;
-            if (meta_data->findInt64(kKeyDriftTime, &driftTimeUs)) {
-                mOwner->setDriftTimeUs(driftTimeUs);
-            }
-        }
 
         if (isSync != 0) {
             addOneStssTableEntry(mNumSamples);
@@ -1735,6 +1889,9 @@
     mReachedEOS = true;
     LOGI("Received total/0-length (%d/%d) buffers and encoded %d frames. - %s",
             count, nZeroLengthFrames, mNumSamples, mIsAudio? "audio": "video");
+    if (mIsAudio) {
+        LOGI("Audio track drift time: %lld us", mOwner->getDriftTimeUs());
+    }
 
     if (err == ERROR_END_OF_STREAM) {
         return OK;
diff --git a/services/java/com/android/server/ClipboardService.java b/services/java/com/android/server/ClipboardService.java
index 308c9c0..bdf313c 100644
--- a/services/java/com/android/server/ClipboardService.java
+++ b/services/java/com/android/server/ClipboardService.java
@@ -16,32 +16,81 @@
 
 package com.android.server;
 
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.IClipboard;
 import android.content.IOnPrimaryClipChangedListener;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.util.Pair;
+import android.util.Slog;
+
+import java.util.HashSet;
 
 /**
  * Implementation of the clipboard for copy and paste.
  */
 public class ClipboardService extends IClipboard.Stub {
-    private ClipData mPrimaryClip;
+    private final Context mContext;
+    private final IActivityManager mAm;
+    private final PackageManager mPm;
+    private final IBinder mPermissionOwner;
+
     private final RemoteCallbackList<IOnPrimaryClipChangedListener> mPrimaryClipListeners
             = new RemoteCallbackList<IOnPrimaryClipChangedListener>();
 
+    private ClipData mPrimaryClip;
+
+    private final HashSet<String> mActivePermissionOwners
+            = new HashSet<String>();
+
     /**
      * Instantiates the clipboard.
      */
-    public ClipboardService(Context context) { }
+    public ClipboardService(Context context) {
+        mContext = context;
+        mAm = ActivityManagerNative.getDefault();
+        mPm = context.getPackageManager();
+        IBinder permOwner = null;
+        try {
+            permOwner = mAm.newUriPermissionOwner("clipboard");
+        } catch (RemoteException e) {
+            Slog.w("clipboard", "AM dead", e);
+        }
+        mPermissionOwner = permOwner;
+    }
+
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        try {
+            return super.onTransact(code, data, reply, flags);
+        } catch (RuntimeException e) {
+            Slog.w("clipboard", "Exception: ", e);
+            throw e;
+        }
+        
+    }
 
     public void setPrimaryClip(ClipData clip) {
         synchronized (this) {
             if (clip != null && clip.getItemCount() <= 0) {
                 throw new IllegalArgumentException("No items");
             }
+            checkDataOwnerLocked(clip, Binder.getCallingUid());
+            clearActiveOwnersLocked();
             mPrimaryClip = clip;
             final int n = mPrimaryClipListeners.beginBroadcast();
             for (int i = 0; i < n; i++) {
@@ -57,8 +106,9 @@
         }
     }
     
-    public ClipData getPrimaryClip() {
+    public ClipData getPrimaryClip(String pkg) {
         synchronized (this) {
+            addActiveOwnerLocked(Binder.getCallingUid(), pkg);
             return mPrimaryClip;
         }
     }
@@ -96,4 +146,110 @@
             return false;
         }
     }
+
+    private final void checkUriOwnerLocked(Uri uri, int uid) {
+        if (!"content".equals(uri.getScheme())) {
+            return;
+        }
+        long ident = Binder.clearCallingIdentity();
+        boolean allowed = false;
+        try {
+            // This will throw SecurityException for us.
+            mAm.checkGrantUriPermission(uid, null, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private final void checkItemOwnerLocked(ClipData.Item item, int uid) {
+        if (item.getUri() != null) {
+            checkUriOwnerLocked(item.getUri(), uid);
+        }
+        Intent intent = item.getIntent();
+        if (intent != null && intent.getData() != null) {
+            checkUriOwnerLocked(intent.getData(), uid);
+        }
+    }
+
+    private final void checkDataOwnerLocked(ClipData data, int uid) {
+        final int N = data.getItemCount();
+        for (int i=0; i<N; i++) {
+            checkItemOwnerLocked(data.getItem(i), uid);
+        }
+    }
+
+    private final void grantUriLocked(Uri uri, String pkg) {
+        long ident = Binder.clearCallingIdentity();
+        try {
+            mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg, uri,
+                    Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private final void grantItemLocked(ClipData.Item item, String pkg) {
+        if (item.getUri() != null) {
+            grantUriLocked(item.getUri(), pkg);
+        }
+        Intent intent = item.getIntent();
+        if (intent != null && intent.getData() != null) {
+            grantUriLocked(intent.getData(), pkg);
+        }
+    }
+
+    private final void addActiveOwnerLocked(int uid, String pkg) {
+        PackageInfo pi;
+        try {
+            pi = mPm.getPackageInfo(pkg, 0);
+            if (pi.applicationInfo.uid != uid) {
+                throw new SecurityException("Calling uid " + uid
+                        + " does not own package " + pkg);
+            }
+        } catch (NameNotFoundException e) {
+            throw new IllegalArgumentException("Unknown package " + pkg, e);
+        }
+        if (!mActivePermissionOwners.contains(pkg)) {
+            final int N = mPrimaryClip.getItemCount();
+            for (int i=0; i<N; i++) {
+                grantItemLocked(mPrimaryClip.getItem(i), pkg);
+            }
+            mActivePermissionOwners.add(pkg);
+        }
+    }
+
+    private final void revokeUriLocked(Uri uri) {
+        long ident = Binder.clearCallingIdentity();
+        try {
+            mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri,
+                    Intent.FLAG_GRANT_READ_URI_PERMISSION
+                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private final void revokeItemLocked(ClipData.Item item) {
+        if (item.getUri() != null) {
+            revokeUriLocked(item.getUri());
+        }
+        Intent intent = item.getIntent();
+        if (intent != null && intent.getData() != null) {
+            revokeUriLocked(intent.getData());
+        }
+    }
+
+    private final void clearActiveOwnersLocked() {
+        mActivePermissionOwners.clear();
+        if (mPrimaryClip == null) {
+            return;
+        }
+        final int N = mPrimaryClip.getItemCount();
+        for (int i=0; i<N; i++) {
+            revokeItemLocked(mPrimaryClip.getItem(i));
+        }
+    }
 }
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 880befd..6095117 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -68,7 +68,7 @@
  */
 public class ConnectivityService extends IConnectivityManager.Stub {
 
-    private static final boolean DBG = false;
+    private static final boolean DBG = true;
     private static final String TAG = "ConnectivityService";
 
     // how long to wait before switching back to a radio's default network
diff --git a/services/java/com/android/server/TelephonyRegistry.java b/services/java/com/android/server/TelephonyRegistry.java
index a33b7c294..2b4845b 100644
--- a/services/java/com/android/server/TelephonyRegistry.java
+++ b/services/java/com/android/server/TelephonyRegistry.java
@@ -584,9 +584,9 @@
         }
         if (linkProperties != null) {
             intent.putExtra(Phone.DATA_LINK_PROPERTIES_KEY, linkProperties);
-            NetworkInterface iface = linkProperties.getInterface();
+            String iface = linkProperties.getInterfaceName();
             if (iface != null) {
-                intent.putExtra(Phone.DATA_IFACE_NAME_KEY, iface.getName());
+                intent.putExtra(Phone.DATA_IFACE_NAME_KEY, iface);
             }
         }
         if (linkCapabilities != null) {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 1eab7fc7..d008c90 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -4350,8 +4350,10 @@
             return -1;
         }
 
-        if (DEBUG_URI_PERMISSION) Slog.v(TAG, 
-                "Checking grant " + targetPkg + " permission to " + uri);
+        if (targetPkg != null) {
+            if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+                    "Checking grant " + targetPkg + " permission to " + uri);
+        }
         
         final IPackageManager pm = AppGlobals.getPackageManager();
 
@@ -4380,23 +4382,45 @@
         }
 
         int targetUid;
-        try {
-            targetUid = pm.getPackageUid(targetPkg);
-            if (targetUid < 0) {
-                if (DEBUG_URI_PERMISSION) Slog.v(TAG, 
-                        "Can't grant URI permission no uid for: " + targetPkg);
+        if (targetPkg != null) {
+            try {
+                targetUid = pm.getPackageUid(targetPkg);
+                if (targetUid < 0) {
+                    if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+                            "Can't grant URI permission no uid for: " + targetPkg);
+                    return -1;
+                }
+            } catch (RemoteException ex) {
                 return -1;
             }
-        } catch (RemoteException ex) {
-            return -1;
+        } else {
+            targetUid = -1;
         }
 
-        // First...  does the target actually need this permission?
-        if (checkHoldingPermissionsLocked(pm, pi, uri, targetUid, modeFlags)) {
-            // No need to grant the target this permission.
-            if (DEBUG_URI_PERMISSION) Slog.v(TAG, 
-                    "Target " + targetPkg + " already has full permission to " + uri);
-            return -1;
+        if (targetUid >= 0) {
+            // First...  does the target actually need this permission?
+            if (checkHoldingPermissionsLocked(pm, pi, uri, targetUid, modeFlags)) {
+                // No need to grant the target this permission.
+                if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+                        "Target " + targetPkg + " already has full permission to " + uri);
+                return -1;
+            }
+        } else {
+            // First...  there is no target package, so can anyone access it?
+            boolean allowed = pi.exported;
+            if ((modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
+                if (pi.readPermission != null) {
+                    allowed = false;
+                }
+            }
+            if ((modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
+                if (pi.writePermission != null) {
+                    allowed = false;
+                }
+            }
+            if (allowed) {
+                return -1;
+            }
         }
 
         // Second...  is the provider allowing granting of URI permissions?
@@ -4426,16 +4450,25 @@
 
         // Third...  does the caller itself have permission to access
         // this uri?
-        if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) {
-            if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) {
-                throw new SecurityException("Uid " + callingUid
-                        + " does not have permission to uri " + uri);
+        if (callingUid != Process.myUid()) {
+            if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) {
+                if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) {
+                    throw new SecurityException("Uid " + callingUid
+                            + " does not have permission to uri " + uri);
+                }
             }
         }
 
         return targetUid;
     }
 
+    public int checkGrantUriPermission(int callingUid, String targetPkg,
+            Uri uri, int modeFlags) {
+        synchronized(this) {
+            return checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags);
+        }
+    }
+
     void grantUriPermissionUncheckedLocked(int targetUid, String targetPkg,
             Uri uri, int modeFlags, UriPermissionOwner owner) {
         modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
@@ -4478,6 +4511,10 @@
 
     void grantUriPermissionLocked(int callingUid,
             String targetPkg, Uri uri, int modeFlags, UriPermissionOwner owner) {
+        if (targetPkg == null) {
+            throw new NullPointerException("targetPkg");
+        }
+
         int targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags);
         if (targetUid < 0) {
             return;
@@ -4496,6 +4533,10 @@
                 + " from " + intent + "; flags=0x"
                 + Integer.toHexString(intent != null ? intent.getFlags() : 0));
 
+        if (targetPkg == null) {
+            throw new NullPointerException("targetPkg");
+        }
+
         if (intent == null) {
             return -1;
         }
diff --git a/services/java/com/android/server/am/UriPermissionOwner.java b/services/java/com/android/server/am/UriPermissionOwner.java
index 99c82e6..68a2e0f 100644
--- a/services/java/com/android/server/am/UriPermissionOwner.java
+++ b/services/java/com/android/server/am/UriPermissionOwner.java
@@ -45,7 +45,7 @@
     }
 
     Binder getExternalTokenLocked() {
-        if (externalToken != null) {
+        if (externalToken == null) {
             externalToken = new ExternalToken();
         }
         return externalToken;
diff --git a/telephony/java/com/android/internal/telephony/DataConnection.java b/telephony/java/com/android/internal/telephony/DataConnection.java
index 3030481..185d413 100644
--- a/telephony/java/com/android/internal/telephony/DataConnection.java
+++ b/telephony/java/com/android/internal/telephony/DataConnection.java
@@ -21,6 +21,7 @@
 import com.android.internal.util.HierarchicalState;
 import com.android.internal.util.HierarchicalStateMachine;
 
+import android.net.LinkAddress;
 import android.net.LinkCapabilities;
 import android.net.LinkProperties;
 import android.os.AsyncResult;
@@ -29,10 +30,10 @@
 import android.util.EventLog;
 
 import java.net.InetAddress;
+import java.net.InterfaceAddress;
 import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.net.UnknownHostException;
-import java.util.HashMap;
 
 /**
  * {@hide}
@@ -68,7 +69,7 @@
  *        EVENT_GET_LAST_FAIL_DONE,
  *        EVENT_DEACTIVATE_DONE.
  *     }
- *   ++ # mInactiveState 
+ *   ++ # mInactiveState
  *        e(doNotifications)
  *        x(clearNotifications) {
  *            EVENT_RESET { notifiyDisconnectCompleted }.
@@ -428,26 +429,25 @@
                 try {
                     String prefix = "net." + interfaceName + ".";
 
-                    linkProperties.setInterface(NetworkInterface.getByName(interfaceName));
+                    NetworkInterface networkInterface = NetworkInterface.getByName(interfaceName);
+                    linkProperties.setInterfaceName(interfaceName);
 
                     // TODO: Get gateway and dns via RIL interface not property?
                     String gatewayAddress = SystemProperties.get(prefix + "gw");
                     linkProperties.setGateway(InetAddress.getByName(gatewayAddress));
 
-                    if (response.length > 2) {
-                        String ipAddress = response[2];
-                        linkProperties.addAddress(InetAddress.getByName(ipAddress));
-
-                        // TODO: Get gateway and dns via RIL interface not property?
-                        String dnsServers[] = new String[2];
-                        dnsServers[0] = SystemProperties.get(prefix + "dns1");
-                        dnsServers[1] = SystemProperties.get(prefix + "dns2");
-                        if (isDnsOk(dnsServers)) {
-                            linkProperties.addDns(InetAddress.getByName(dnsServers[0]));
-                            linkProperties.addDns(InetAddress.getByName(dnsServers[1]));
-                        } else {
-                            result = SetupResult.ERR_BadDns;
-                        }
+                    for (InterfaceAddress addr : networkInterface.getInterfaceAddresses()) {
+                        linkProperties.addLinkAddress(new LinkAddress(addr));
+                    }
+                    // TODO: Get gateway and dns via RIL interface not property?
+                    String dnsServers[] = new String[2];
+                    dnsServers[0] = SystemProperties.get(prefix + "dns1");
+                    dnsServers[1] = SystemProperties.get(prefix + "dns2");
+                    if (isDnsOk(dnsServers)) {
+                        linkProperties.addDns(InetAddress.getByName(dnsServers[0]));
+                        linkProperties.addDns(InetAddress.getByName(dnsServers[1]));
+                    } else {
+                        result = SetupResult.ERR_BadDns;
                     }
                 } catch (UnknownHostException e1) {
                     log("onSetupCompleted: UnknowHostException " + e1);
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java
index 4430533..0dc836d 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java
@@ -101,6 +101,8 @@
             mSmallPaint.setAntiAlias(false);
             canvas.drawLine(0.0f, 0.0f, 400.0f, 0.0f, mSmallPaint);
             mSmallPaint.setAntiAlias(true);
+            canvas.drawLine(0.0f, 0.0f, 0.0f, 400.0f, mSmallPaint);
+            canvas.drawLine(0.0f, 400.0f, 400.0f, 400.0f, mSmallPaint);
             
             canvas.translate(120.0f, 0.0f);
             mAlphaPaint.setShader(mShader);            
diff --git a/voip/jni/rtp/AmrCodec.cpp b/voip/jni/rtp/AmrCodec.cpp
index f3ecac2..84c7166 100644
--- a/voip/jni/rtp/AmrCodec.cpp
+++ b/voip/jni/rtp/AmrCodec.cpp
@@ -73,7 +73,7 @@
     }
 
     // Handle mode-set and octet-align.
-    char *modes = strcasestr(fmtp, "mode-set=");
+    const char *modes = strcasestr(fmtp, "mode-set=");
     if (modes) {
         mMode = 0;
         mModeSet = 0;
diff --git a/voip/jni/rtp/EchoSuppressor.cpp b/voip/jni/rtp/EchoSuppressor.cpp
index 92015a9..2ceebdc 100644
--- a/voip/jni/rtp/EchoSuppressor.cpp
+++ b/voip/jni/rtp/EchoSuppressor.cpp
@@ -16,6 +16,7 @@
 
 #include <stdio.h>
 #include <stdint.h>
+#include <string.h>
 #include <math.h>
 
 #define LOG_TAG "Echo"
diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java
index 7ea4872..9634157 100644
--- a/wifi/java/android/net/wifi/WifiConfigStore.java
+++ b/wifi/java/android/net/wifi/WifiConfigStore.java
@@ -20,19 +20,24 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.DhcpInfo;
+import android.net.ProxyProperties;
 import android.net.wifi.WifiConfiguration.IpAssignment;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
+import android.net.wifi.WifiConfiguration.ProxySettings;
 import android.net.wifi.WifiConfiguration.Status;
 import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
 import android.os.Environment;
 import android.text.TextUtils;
 import android.util.Log;
 
-import java.io.BufferedWriter;
-import java.io.File;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
 import java.io.FileInputStream;
-import java.io.FileWriter;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.net.InetSocketAddress;
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.HashMap;
@@ -46,8 +51,31 @@
  * It deals with the following
  * - Add/update/remove a WifiConfiguration
  *   The configuration contains two types of information.
- *     = IP configuration that is handled by WifiConfigStore and
+ *     = IP and proxy configuration that is handled by WifiConfigStore and
  *       is saved to disk on any change.
+ *
+ *       The format of configuration file is as follows:
+ *       <version>
+ *       <netA_key1><netA_value1><netA_key2><netA_value2>...<EOS>
+ *       <netB_key1><netB_value1><netB_key2><netB_value2>...<EOS>
+ *       ..
+ *
+ *       (key, value) pairs for a given network are grouped together and can
+ *       be in any order. A "EOS" at the end of a set of (key, value) pairs
+ *       indicates that the next set of (key, value) pairs are for a new
+ *       network. A network is identified by a unique "id". If there is no
+ *       "id" key in the (key, value) pairs, the data is discarded. An IP
+ *       configuration includes the keys - "ipAssignment", "ipAddress", "gateway",
+ *       "netmask", "dns1" and "dns2". A proxy configuration includes "proxySettings",
+ *       "proxyHost", "proxyPort" and "exclusionList"
+ *
+ *       An invalid version on read would result in discarding the contents of
+ *       the file. On the next write, the latest version is written to file.
+ *
+ *       Any failures during read or write to the configuration file are ignored
+ *       without reporting to the user since the likelihood of these errors are
+ *       low and the impact on connectivity is low.
+ *
  *     = SSID & security details that is pushed to the supplicant.
  *       supplicant saves these details to the disk on calling
  *       saveConfigCommand().
@@ -59,10 +87,9 @@
  *          to the disk. (TODO: deprecate these calls in WifiManager)
  *        > The new API calls - selectNetwork(), saveNetwork() & forgetNetwork().
  *          These calls persist the supplicant config to disk.
+ *
  * - Maintain a list of configured networks for quick access
  *
- * TODO:
- * - handle proxy per configuration
  */
 class WifiConfigStore {
 
@@ -110,7 +137,7 @@
         List<WifiConfiguration> networks = new ArrayList<WifiConfiguration>();
         synchronized (sConfiguredNetworks) {
             for(WifiConfiguration config : sConfiguredNetworks.values()) {
-                networks.add(config.clone());
+                networks.add(new WifiConfiguration(config));
             }
         }
         return networks;
@@ -229,6 +256,7 @@
             synchronized (sConfiguredNetworks) {
                 sConfiguredNetworks.remove(netId);
             }
+            writeIpAndProxyConfigurations();
             sendConfigChangeBroadcast();
         } else {
             Log.e(TAG, "Failed to remove network " + netId);
@@ -353,6 +381,19 @@
     }
 
     /**
+     * Fetch the proxy properties for a given network id
+     */
+    static ProxyProperties getProxyProperties(int netId) {
+        synchronized (sConfiguredNetworks) {
+            WifiConfiguration config = sConfiguredNetworks.get(netId);
+            if (config != null && config.proxySettings == ProxySettings.STATIC) {
+                return new ProxyProperties(config.proxyProperties);
+            }
+        }
+        return null;
+    }
+
+    /**
      * Return if the specified network is using static IP
      */
     static boolean isUsingStaticIp(int netId) {
@@ -411,7 +452,7 @@
                 sNetworkIds.put(configKey(config), config.networkId);
             }
         }
-        readIpConfigurations();
+        readIpAndProxyConfigurations();
         sendConfigChangeBroadcast();
     }
 
@@ -430,38 +471,89 @@
         markAllNetworksDisabledExcept(INVALID_NETWORK_ID);
     }
 
-    private static void writeIpConfigurations() {
-        StringBuilder builder = new StringBuilder();
-        BufferedWriter out = null;
+    private static void writeIpAndProxyConfigurations() {
 
-        builder.append(IPCONFIG_FILE_VERSION);
-        builder.append("\n");
+        DataOutputStream out = null;
+        try {
+            out = new DataOutputStream(new BufferedOutputStream(
+                    new FileOutputStream(ipConfigFile)));
 
-        synchronized (sConfiguredNetworks) {
-            for(WifiConfiguration config : sConfiguredNetworks.values()) {
-                if (config.ipAssignment == WifiConfiguration.IpAssignment.STATIC) {
-                    builder.append("id=" + configKey(config));
-                    builder.append(":");
-                    builder.append("ip=" + config.ipConfig.ipAddress);
-                    builder.append(":");
-                    builder.append("gateway=" + config.ipConfig.gateway);
-                    builder.append(":");
-                    builder.append("netmask=" + config.ipConfig.netmask);
-                    builder.append(":");
-                    builder.append("dns1=" + config.ipConfig.dns1);
-                    builder.append(":");
-                    builder.append("dns2=" + config.ipConfig.dns2);
-                    builder.append("\n");
+            out.writeInt(IPCONFIG_FILE_VERSION);
+
+            synchronized (sConfiguredNetworks) {
+                for(WifiConfiguration config : sConfiguredNetworks.values()) {
+                    boolean writeToFile = false;
+
+                    switch (config.ipAssignment) {
+                        case STATIC:
+                            out.writeUTF("ipAssignment");
+                            out.writeUTF(config.ipAssignment.toString());
+                            out.writeUTF("ipAddress");
+                            out.writeInt(config.ipConfig.ipAddress);
+                            out.writeUTF("gateway");
+                            out.writeInt(config.ipConfig.gateway);
+                            out.writeUTF("netmask");
+                            out.writeInt(config.ipConfig.netmask);
+                            out.writeUTF("dns1");
+                            out.writeInt(config.ipConfig.dns1);
+                            out.writeUTF("dns2");
+                            out.writeInt(config.ipConfig.dns2);
+                            writeToFile = true;
+                            break;
+                        case DHCP:
+                            out.writeUTF("ipAssignment");
+                            out.writeUTF(config.ipAssignment.toString());
+                            writeToFile = true;
+                            break;
+                        case UNASSIGNED:
+                            /* Ignore */
+                            break;
+                        default:
+                            Log.e(TAG, "Ignore invalid ip assignment while writing");
+                            break;
+                    }
+
+                    switch (config.proxySettings) {
+                        case STATIC:
+                            out.writeUTF("proxySettings");
+                            out.writeUTF(config.proxySettings.toString());
+                            InetSocketAddress proxy = config.proxyProperties.getSocketAddress();
+                            if (proxy != null) {
+                                out.writeUTF("proxyHost");
+                                out.writeUTF(proxy.getHostName());
+                                out.writeUTF("proxyPort");
+                                out.writeInt(proxy.getPort());
+                                String exclusionList = config.proxyProperties.getExclusionList();
+                                if (exclusionList != null && exclusionList.length() > 0) {
+                                    out.writeUTF("exclusionList");
+                                    out.writeUTF(exclusionList);
+                                }
+                            }
+                            writeToFile = true;
+                            break;
+                        case NONE:
+                            out.writeUTF("proxySettings");
+                            out.writeUTF(config.proxySettings.toString());
+                            writeToFile = true;
+                            break;
+                        case UNASSIGNED:
+                            /* Ignore */
+                            break;
+                        default:
+                            Log.e(TAG, "Ignore invalid proxy settings while writing");
+                            break;
+                    }
+
+                    if (writeToFile) {
+                        out.writeUTF("id");
+                        out.writeInt(configKey(config));
+                        out.writeUTF("EOS");
+                    }
                 }
             }
-        }
 
-        try {
-            out = new BufferedWriter(new FileWriter(ipConfigFile), builder.length());
-            out.write(builder.toString());
         } catch (IOException e) {
             Log.e(TAG, "Error writing data file");
-            return;
         } finally {
             if (out != null) {
                 try {
@@ -471,80 +563,116 @@
         }
     }
 
-    private static void readIpConfigurations() {
-        File f = new File(ipConfigFile);
-        byte[] buffer;
-        FileInputStream s = null;
-        try {
-            buffer = new byte[(int)f.length()];
-            s = new FileInputStream(f);
-            s.read(buffer);
-        } catch (IOException e) {
-            Log.e(TAG, "Error reading data file");
-            return;
-        } finally {
-            if (s != null) {
-                try {
-                    s.close();
-                } catch (Exception e) {}
-            }
-        }
+    private static void readIpAndProxyConfigurations() {
 
-        String data = new String(buffer);
-        if (data == null || data.length() == 0) {
-            Log.d(TAG, "IP configuration file empty");
-            return;
-        }
-
-        String[] parsed = data.split("\n");
+        DataInputStream in = null;
         try {
-            if (Integer.parseInt(parsed[0]) != IPCONFIG_FILE_VERSION) {
+            in = new DataInputStream(new BufferedInputStream(new FileInputStream(
+                    ipConfigFile)));
+
+            if (in.readInt() != IPCONFIG_FILE_VERSION) {
                 Log.e(TAG, "Bad version on IP configuration file, ignore read");
                 return;
             }
 
-            for (String line : parsed) {
-                int hashKey = -1;
+            while (true) {
+                int id = -1;
+                IpAssignment ipAssignment = IpAssignment.UNASSIGNED;
                 DhcpInfo ipConfig = new DhcpInfo();
-                String[] keyVals = line.split(":");
+                ProxySettings proxySettings = ProxySettings.UNASSIGNED;
+                String proxyHost = null;
+                int proxyPort = -1;
+                String exclusionList = null;
+                String key;
 
-                for (String keyVal : keyVals) {
-                    String[] keyValPair = keyVal.split("=");
-                    if (keyValPair[0].equals("id")) {
-                        hashKey = Integer.parseInt(keyValPair[1]);
-                    } else if (keyValPair[0].equals("ip")) {
-                        ipConfig.ipAddress = Integer.parseInt(keyValPair[1]);
-                    } else if (keyValPair[0].equals("gateway")) {
-                        ipConfig.gateway = Integer.parseInt(keyValPair[1]);
-                    } else if (keyValPair[0].equals("netmask")) {
-                        ipConfig.netmask = Integer.parseInt(keyValPair[1]);
-                    } else if (keyValPair[0].equals("dns1")) {
-                        ipConfig.dns1 = Integer.parseInt(keyValPair[1]);
-                    } else if (keyValPair[0].equals("dns2")) {
-                        ipConfig.dns2 = Integer.parseInt(keyValPair[1]);
+                do {
+                    key = in.readUTF();
+                    if (key.equals("id")) {
+                        id = in.readInt();
+                    } else if (key.equals("ipAssignment")) {
+                        ipAssignment = IpAssignment.valueOf(in.readUTF());
+                    } else if (key.equals("ipAddress")) {
+                        ipConfig.ipAddress = in.readInt();
+                    } else if (key.equals("gateway")) {
+                        ipConfig.gateway = in.readInt();
+                    } else if (key.equals("netmask")) {
+                        ipConfig.netmask = in.readInt();
+                    } else if (key.equals("dns1")) {
+                        ipConfig.dns1 = in.readInt();
+                    } else if (key.equals("dns2")) {
+                        ipConfig.dns2 = in.readInt();
+                    } else if (key.equals("proxySettings")) {
+                        proxySettings = ProxySettings.valueOf(in.readUTF());
+                    } else if (key.equals("proxyHost")) {
+                        proxyHost = in.readUTF();
+                    } else if (key.equals("proxyPort")) {
+                        proxyPort = in.readInt();
+                    } else if (key.equals("exclusionList")) {
+                        exclusionList = in.readUTF();
+                    } else if (key.equals("EOS")) {
+                        break;
                     } else {
-                        Log.w(TAG, "Ignoring " + keyVal);
+                        Log.e(TAG, "Ignore unknown key " + key + "while reading");
                     }
-                }
+                } while (true);
 
-                if (hashKey != -1) {
+                if (id != -1) {
                     synchronized (sConfiguredNetworks) {
                         WifiConfiguration config = sConfiguredNetworks.get(
-                                sNetworkIds.get(hashKey));
+                                sNetworkIds.get(id));
 
                         if (config == null) {
-                            Log.e(TAG, "IP configuration found for missing network, ignored");
+                            Log.e(TAG, "configuration found for missing network, ignored");
                         } else {
-                            config.ipAssignment = WifiConfiguration.IpAssignment.STATIC;
-                            config.ipConfig = ipConfig;
+                            switch (ipAssignment) {
+                                case STATIC:
+                                    config.ipAssignment = ipAssignment;
+                                    config.ipConfig = ipConfig;
+                                    break;
+                                case DHCP:
+                                    config.ipAssignment = ipAssignment;
+                                    break;
+                                case UNASSIGNED:
+                                    //Ignore
+                                    break;
+                                default:
+                                    Log.e(TAG, "Ignore invalid ip assignment while reading");
+                                    break;
+                            }
+
+                            switch (proxySettings) {
+                                case STATIC:
+                                    config.proxySettings = proxySettings;
+                                    ProxyProperties proxyProperties = new ProxyProperties();
+                                    proxyProperties.setSocketAddress(
+                                            new InetSocketAddress(proxyHost, proxyPort));
+                                    proxyProperties.setExclusionList(exclusionList);
+                                    config.proxyProperties = proxyProperties;
+                                    break;
+                                case NONE:
+                                    config.proxySettings = proxySettings;
+                                    break;
+                                case UNASSIGNED:
+                                    //Ignore
+                                    break;
+                                default:
+                                    Log.e(TAG, "Ignore invalid proxy settings while reading");
+                                    break;
+                            }
                         }
                     }
                 } else {
-                    Log.e(TAG,"Missing id while parsing configuration" + line);
+                    Log.e(TAG,"Missing id while parsing configuration");
                 }
             }
-        } catch (NumberFormatException e) {
+        } catch (IOException e) {
             Log.e(TAG, "Error parsing configuration");
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (Exception e) {}
+            }
         }
     }
 
@@ -759,21 +887,68 @@
             }
         }
         readNetworkVariables(sConfig);
+        writeIpAndProxyConfigurationsOnChange(sConfig, config);
+        return netId;
+    }
 
-        if (config.ipAssignment != IpAssignment.UNASSIGNED) {
+    /* Compare current and new configuration and write to file on change */
+    private static void writeIpAndProxyConfigurationsOnChange(WifiConfiguration currentConfig,
+            WifiConfiguration newConfig) {
+        boolean newNetwork = (newConfig.networkId == INVALID_NETWORK_ID);
+        boolean writeConfigToFile = false;
+
+        if (newConfig.ipAssignment != IpAssignment.UNASSIGNED) {
             if (newNetwork ||
-                    (sConfig.ipAssignment != config.ipAssignment) ||
-                    (sConfig.ipConfig.ipAddress != config.ipConfig.ipAddress) ||
-                    (sConfig.ipConfig.gateway != config.ipConfig.gateway) ||
-                    (sConfig.ipConfig.netmask != config.ipConfig.netmask) ||
-                    (sConfig.ipConfig.dns1 != config.ipConfig.dns1) ||
-                    (sConfig.ipConfig.dns2 != config.ipConfig.dns2)) {
-                sConfig.ipAssignment = config.ipAssignment;
-                sConfig.ipConfig = config.ipConfig;
-                writeIpConfigurations();
+                    (currentConfig.ipAssignment != newConfig.ipAssignment) ||
+                    (currentConfig.ipConfig.ipAddress != newConfig.ipConfig.ipAddress) ||
+                    (currentConfig.ipConfig.gateway != newConfig.ipConfig.gateway) ||
+                    (currentConfig.ipConfig.netmask != newConfig.ipConfig.netmask) ||
+                    (currentConfig.ipConfig.dns1 != newConfig.ipConfig.dns1) ||
+                    (currentConfig.ipConfig.dns2 != newConfig.ipConfig.dns2)) {
+                currentConfig.ipAssignment = newConfig.ipAssignment;
+                currentConfig.ipConfig = newConfig.ipConfig;
+                writeConfigToFile = true;
             }
         }
-        return netId;
+
+        if (newConfig.proxySettings != ProxySettings.UNASSIGNED) {
+            InetSocketAddress newSockAddr = newConfig.proxyProperties.getSocketAddress();
+            String newExclusionList = newConfig.proxyProperties.getExclusionList();
+
+            InetSocketAddress currentSockAddr = currentConfig.proxyProperties.getSocketAddress();
+            String currentExclusionList = currentConfig.proxyProperties.getExclusionList();
+
+            boolean socketAddressDiffers = false;
+            boolean exclusionListDiffers = false;
+
+            if (newSockAddr != null && currentSockAddr != null ) {
+                socketAddressDiffers = !currentSockAddr.equals(newSockAddr);
+            } else if (newSockAddr != null || currentSockAddr != null) {
+                socketAddressDiffers = true;
+            }
+
+            if (newExclusionList != null && currentExclusionList != null) {
+                exclusionListDiffers = currentExclusionList.equals(newExclusionList);
+            } else if (newExclusionList != null || currentExclusionList != null) {
+                exclusionListDiffers = true;
+            }
+
+            if (newNetwork ||
+                    (currentConfig.proxySettings != newConfig.proxySettings) ||
+                    socketAddressDiffers ||
+                    exclusionListDiffers) {
+                currentConfig.proxySettings = newConfig.proxySettings;
+                currentConfig.proxyProperties = newConfig.proxyProperties;
+                Log.d(TAG, "proxy change SSID = " + currentConfig.SSID + " proxyProperties: " +
+                        currentConfig.proxyProperties.toString());
+                writeConfigToFile = true;
+            }
+        }
+
+        if (writeConfigToFile) {
+            writeIpAndProxyConfigurations();
+            sendConfigChangeBroadcast();
+        }
     }
 
     /**
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 57e9bad..c4a1310 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -17,6 +17,7 @@
 package android.net.wifi;
 
 import android.net.DhcpInfo;
+import android.net.ProxyProperties;
 import android.os.Parcelable;
 import android.os.Parcel;
 
@@ -301,8 +302,13 @@
      * @hide
      */
     public enum IpAssignment {
+        /* Use statically configured IP settings. Configuration can be accessed
+         * with ipConfig */
         STATIC,
+        /* Use dynamically configured IP settigns */
         DHCP,
+        /* no IP details are assigned, this is used to indicate
+         * that any existing IP settings should be retained */
         UNASSIGNED
     }
     /**
@@ -314,6 +320,29 @@
      */
     public DhcpInfo ipConfig;
 
+    /**
+     * @hide
+     */
+    public enum ProxySettings {
+        /* No proxy is to be used. Any existing proxy settings
+         * should be cleared. */
+        NONE,
+        /* Use statically configured proxy. Configuration can be accessed
+         * with proxyProperties */
+        STATIC,
+        /* no proxy details are assigned, this is used to indicate
+         * that any existing proxy settings should be retained */
+        UNASSIGNED
+    }
+    /**
+     * @hide
+     */
+    public ProxySettings proxySettings;
+    /**
+     * @hide
+     */
+    public ProxyProperties proxyProperties;
+
     public WifiConfiguration() {
         networkId = INVALID_NETWORK_ID;
         SSID = null;
@@ -333,6 +362,8 @@
         }
         ipAssignment = IpAssignment.UNASSIGNED;
         ipConfig = new DhcpInfo();
+        proxySettings = ProxySettings.UNASSIGNED;
+        proxyProperties = new ProxyProperties();
     }
 
     public String toString() {
@@ -419,6 +450,12 @@
             sbuf.append(" ").append(ipConfig);
         }
         sbuf.append('\n');
+
+        if (proxySettings == ProxySettings.STATIC) {
+            sbuf.append(" ").append("Proxy configuration:").append('\n');
+            sbuf.append(" ").append(proxyProperties);
+        }
+        sbuf.append('\n');
         return sbuf.toString();
     }
 
@@ -458,38 +495,36 @@
         return 0;
     }
 
-    /**
-     * Returns a copy of this WifiConfiguration.
-     *
-     * @return a copy of this WifiConfiguration.
-     * @hide
-     */
-    public WifiConfiguration clone() {
-        WifiConfiguration config = new WifiConfiguration();
-        config.networkId = networkId;
-        config.status = status;
-        config.SSID = SSID;
-        config.BSSID = BSSID;
-        config.preSharedKey = preSharedKey;
+    /** copy constructor {@hide} */
+    public WifiConfiguration(WifiConfiguration source) {
+        if (source != null) {
+            networkId = source.networkId;
+            status = source.status;
+            SSID = source.SSID;
+            BSSID = source.BSSID;
+            preSharedKey = source.preSharedKey;
 
-        for (int i = 0; i < wepKeys.length; i++)
-            config.wepKeys[i] = wepKeys[i];
+            wepKeys = new String[4];
+            for (int i = 0; i < wepKeys.length; i++)
+                wepKeys[i] = source.wepKeys[i];
 
-        config.wepTxKeyIndex = wepTxKeyIndex;
-        config.priority = priority;
-        config.hiddenSSID = hiddenSSID;
-        config.allowedKeyManagement   = (BitSet) allowedKeyManagement.clone();
-        config.allowedProtocols       = (BitSet) allowedProtocols.clone();
-        config.allowedAuthAlgorithms  = (BitSet) allowedAuthAlgorithms.clone();
-        config.allowedPairwiseCiphers = (BitSet) allowedPairwiseCiphers.clone();
-        config.allowedGroupCiphers    = (BitSet) allowedGroupCiphers.clone();
+            wepTxKeyIndex = source.wepTxKeyIndex;
+            priority = source.priority;
+            hiddenSSID = source.hiddenSSID;
+            allowedKeyManagement   = (BitSet) source.allowedKeyManagement.clone();
+            allowedProtocols       = (BitSet) source.allowedProtocols.clone();
+            allowedAuthAlgorithms  = (BitSet) source.allowedAuthAlgorithms.clone();
+            allowedPairwiseCiphers = (BitSet) source.allowedPairwiseCiphers.clone();
+            allowedGroupCiphers    = (BitSet) source.allowedGroupCiphers.clone();
 
-        for (int i = 0; i < enterpriseFields.length; i++) {
-            config.enterpriseFields[i].setValue(enterpriseFields[i].value());
+            for (int i = 0; i < source.enterpriseFields.length; i++) {
+                enterpriseFields[i].setValue(source.enterpriseFields[i].value());
+            }
+            ipAssignment = source.ipAssignment;
+            ipConfig = new DhcpInfo(source.ipConfig);
+            proxySettings = source.proxySettings;
+            proxyProperties = new ProxyProperties(source.proxyProperties);
         }
-        config.ipAssignment = ipAssignment;
-        config.ipConfig = new DhcpInfo(ipConfig);
-        return config;
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -522,6 +557,8 @@
         dest.writeInt(ipConfig.dns2);
         dest.writeInt(ipConfig.serverAddress);
         dest.writeInt(ipConfig.leaseDuration);
+        dest.writeString(proxySettings.name());
+        dest.writeParcelable(proxyProperties, flags);
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -557,6 +594,8 @@
                 config.ipConfig.dns2 = in.readInt();
                 config.ipConfig.serverAddress = in.readInt();
                 config.ipConfig.leaseDuration = in.readInt();
+                config.proxySettings = ProxySettings.valueOf(in.readString());
+                config.proxyProperties = in.readParcelable(null);
                 return config;
             }
 
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index e82c003..572abc0 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -38,12 +38,14 @@
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
 
 import android.app.ActivityManagerNative;
+import android.net.LinkAddress;
 import android.net.NetworkInfo;
 import android.net.DhcpInfo;
 import android.net.NetworkUtils;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo.DetailedState;
 import android.net.LinkProperties;
+import android.net.ProxyProperties;
 import android.net.wifi.WifiConfiguration.Status;
 import android.os.Binder;
 import android.os.Message;
@@ -1251,24 +1253,25 @@
     }
 
     private void configureLinkProperties() {
-        try {
-            mLinkProperties.setInterface(NetworkInterface.getByName(mInterfaceName));
-        } catch (SocketException e) {
-            Log.e(TAG, "SocketException creating NetworkInterface from " + mInterfaceName +
-                    ". e=" + e);
-            return;
-        } catch (NullPointerException e) {
-            Log.e(TAG, "NPE creating NetworkInterface. e=" + e);
-            return;
-        }
+
+        mLinkProperties.setInterfaceName(mInterfaceName);
+
         // TODO - fix this for v6
         synchronized (mDhcpInfo) {
-            mLinkProperties.addAddress(NetworkUtils.intToInetAddress(mDhcpInfo.ipAddress));
+            mLinkProperties.addLinkAddress(new LinkAddress(
+                    NetworkUtils.intToInetAddress(mDhcpInfo.ipAddress),
+                    NetworkUtils.intToInetAddress(mDhcpInfo.netmask)));
             mLinkProperties.setGateway(NetworkUtils.intToInetAddress(mDhcpInfo.gateway));
             mLinkProperties.addDns(NetworkUtils.intToInetAddress(mDhcpInfo.dns1));
             mLinkProperties.addDns(NetworkUtils.intToInetAddress(mDhcpInfo.dns2));
         }
-        // TODO - add proxy info
+
+        ProxyProperties proxyProperties = WifiConfigStore.getProxyProperties(mLastNetworkId);
+        if (proxyProperties != null) {
+            mLinkProperties.setHttpProxy(proxyProperties);
+            Log.d(TAG, "netId=" + mLastNetworkId  + " proxy configured: "
+                    + proxyProperties.toString());
+        }
     }
 
     private int getMaxDhcpRetries() {