Merge "Fix 5156702: rotate thumbnails"
diff --git a/CleanSpec.mk b/CleanSpec.mk
index e6c4183..c1799a1 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -108,6 +108,7 @@
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/media/java/android/media/IAudioService.P)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/media/audio/)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/nfc/)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/wifi/java)
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
 # ************************************************
diff --git a/api/current.txt b/api/current.txt
index f8b7bd9..b0621c2 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2452,6 +2452,8 @@
     method public abstract void setLogo(android.graphics.drawable.Drawable);
     method public abstract void setNavigationMode(int);
     method public abstract void setSelectedNavigationItem(int);
+    method public void setSplitBackgroundDrawable(android.graphics.drawable.Drawable);
+    method public void setStackedBackgroundDrawable(android.graphics.drawable.Drawable);
     method public abstract void setSubtitle(java.lang.CharSequence);
     method public abstract void setSubtitle(int);
     method public abstract void setTitle(java.lang.CharSequence);
@@ -2487,12 +2489,15 @@
 
   public static abstract class ActionBar.Tab {
     ctor public ActionBar.Tab();
+    method public abstract java.lang.CharSequence getContentDescription();
     method public abstract android.view.View getCustomView();
     method public abstract android.graphics.drawable.Drawable getIcon();
     method public abstract int getPosition();
     method public abstract java.lang.Object getTag();
     method public abstract java.lang.CharSequence getText();
     method public abstract void select();
+    method public abstract android.app.ActionBar.Tab setContentDescription(int);
+    method public abstract android.app.ActionBar.Tab setContentDescription(java.lang.CharSequence);
     method public abstract android.app.ActionBar.Tab setCustomView(android.view.View);
     method public abstract android.app.ActionBar.Tab setCustomView(int);
     method public abstract android.app.ActionBar.Tab setIcon(android.graphics.drawable.Drawable);
@@ -6211,6 +6216,7 @@
     field public static final java.lang.String FEATURE_USB_ACCESSORY = "android.hardware.usb.accessory";
     field public static final java.lang.String FEATURE_USB_HOST = "android.hardware.usb.host";
     field public static final java.lang.String FEATURE_WIFI = "android.hardware.wifi";
+    field public static final java.lang.String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct";
     field public static final int GET_ACTIVITIES = 1; // 0x1
     field public static final int GET_CONFIGURATIONS = 16384; // 0x4000
     field public static final int GET_DISABLED_COMPONENTS = 512; // 0x200
@@ -8893,7 +8899,6 @@
     method public int getMinimumWidth();
     method public abstract int getOpacity();
     method public boolean getPadding(android.graphics.Rect);
-    method public int getResolvedLayoutDirectionSelf();
     method public int[] getState();
     method public android.graphics.Region getTransparentRegion();
     method public void inflate(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
@@ -8928,10 +8933,6 @@
     method public abstract void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
   }
 
-  public static abstract interface Drawable.Callback2 implements android.graphics.drawable.Drawable.Callback {
-    method public abstract int getResolvedLayoutDirection(android.graphics.drawable.Drawable);
-  }
-
   public static abstract class Drawable.ConstantState {
     ctor public Drawable.ConstantState();
     method public abstract int getChangingConfigurations();
@@ -9323,6 +9324,7 @@
     method public boolean isAutoExposureLockSupported();
     method public boolean isAutoWhiteBalanceLockSupported();
     method public boolean isSmoothZoomSupported();
+    method public boolean isVideoSnapshotSupported();
     method public boolean isZoomSupported();
     method public void remove(java.lang.String);
     method public void removeGpsData();
@@ -22662,7 +22664,7 @@
     method public void recycle();
   }
 
-  public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback2 android.view.KeyEvent.Callback {
+  public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
     ctor public View(android.content.Context);
     ctor public View(android.content.Context, android.util.AttributeSet);
     ctor public View(android.content.Context, android.util.AttributeSet, int);
@@ -22679,7 +22681,6 @@
     method public void buildDrawingCache();
     method public void buildDrawingCache(boolean);
     method public void buildLayer();
-    method protected boolean canResolveLayoutDirection();
     method public boolean canScrollHorizontally(int);
     method public boolean canScrollVertically(int);
     method public void cancelLongPress();
@@ -22791,7 +22792,6 @@
     method public final android.view.ViewParent getParent();
     method public float getPivotX();
     method public float getPivotY();
-    method public int getResolvedLayoutDirection(android.graphics.drawable.Drawable);
     method public android.content.res.Resources getResources();
     method public final int getRight();
     method protected float getRightFadingEdgeStrength();
@@ -22856,7 +22856,6 @@
     method public boolean isHovered();
     method public boolean isInEditMode();
     method public boolean isInTouchMode();
-    method protected static boolean isLayoutDirectionRtl(java.util.Locale);
     method public boolean isLayoutRequested();
     method public boolean isLongClickable();
     method public boolean isOpaque();
@@ -22942,10 +22941,8 @@
     method public void requestLayout();
     method public boolean requestRectangleOnScreen(android.graphics.Rect);
     method public boolean requestRectangleOnScreen(android.graphics.Rect, boolean);
-    method protected void resetResolvedTextDirection();
     method public static int resolveSize(int, int);
     method public static int resolveSizeAndState(int, int, int);
-    method protected void resolveTextDirection();
     method public void restoreHierarchyState(android.util.SparseArray<android.os.Parcelable>);
     method public void saveHierarchyState(android.util.SparseArray<android.os.Parcelable>);
     method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
@@ -23046,7 +23043,6 @@
     method public boolean willNotCacheDrawing();
     method public boolean willNotDraw();
     field public static android.util.Property ALPHA;
-    field protected static int DEFAULT_TEXT_DIRECTION;
     field public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0
     field public static final int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000
     field public static final int DRAWING_CACHE_QUALITY_LOW = 524288; // 0x80000
@@ -23375,6 +23371,7 @@
     method public boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
     method public void requestTransparentRegion(android.view.View);
     method protected void resetResolvedLayoutDirection();
+    method protected void resetResolvedTextDirection();
     method public void scheduleLayoutAnimation();
     method public void setAddStatesFromChildren(boolean);
     method public void setAlwaysDrawnWithCacheEnabled(boolean);
@@ -27172,6 +27169,7 @@
     method protected void resetResolvedDrawables();
     method protected void resetResolvedLayoutDirection();
     method protected void resolveDrawables();
+    method protected void resolveTextDirection();
     method public void setAllCaps(boolean);
     method public final void setAutoLinkMask(int);
     method public void setCompoundDrawablePadding(int);
diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java
index 9e2b833..355b1fc 100644
--- a/core/java/android/animation/LayoutTransition.java
+++ b/core/java/android/animation/LayoutTransition.java
@@ -793,7 +793,9 @@
      * @hide
      */
     public void startChangingAnimations() {
-        for (Animator anim : currentChangingAnimations.values()) {
+        LinkedHashMap<View, Animator> currentAnimCopy =
+                (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
+        for (Animator anim : currentAnimCopy.values()) {
             if (anim instanceof ObjectAnimator) {
                 ((ObjectAnimator) anim).setCurrentPlayTime(0);
             }
@@ -802,6 +804,23 @@
     }
 
     /**
+     * Ends the animations that are set up for a CHANGING transition. This is a variant of
+     * startChangingAnimations() which is called when the window the transition is playing in
+     * is not visible. We need to make sure the animations put their targets in their end states
+     * and that the transition finishes to remove any mid-process state (such as isRunning()).
+     *
+     * @hide
+     */
+    public void endChangingAnimations() {
+        LinkedHashMap<View, Animator> currentAnimCopy =
+                (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
+        for (Animator anim : currentAnimCopy.values()) {
+            anim.start();
+            anim.end();
+        }
+    }
+
+    /**
      * Returns true if animations are running which animate layout-related properties. This
      * essentially means that either CHANGE_APPEARING or CHANGE_DISAPPEARING animations
      * are running, since these animations operate on layout-related properties.
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index 7acaec8..0d4a067 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -402,13 +402,33 @@
     public abstract void setDisplayShowCustomEnabled(boolean showCustom);
 
     /**
-     * Set the ActionBar's background.
+     * Set the ActionBar's background. This will be used for the primary
+     * action bar.
      * 
      * @param d Background drawable
+     * @see #setStackedBackgroundDrawable(Drawable)
+     * @see #setSplitBackgroundDrawable(Drawable)
      */
     public abstract void setBackgroundDrawable(Drawable d);
 
     /**
+     * Set the ActionBar's stacked background. This will appear
+     * in the second row/stacked bar on some devices and configurations.
+     *
+     * @param d Background drawable for the stacked row
+     */
+    public void setStackedBackgroundDrawable(Drawable d) { }
+
+    /**
+     * Set the ActionBar's split background. This will appear in
+     * the split action bar containing menu-provided action buttons
+     * on some devices and configurations.
+     *
+     * @param d Background drawable for the split bar
+     */
+    public void setSplitBackgroundDrawable(Drawable d) { }
+
+    /**
      * @return The current custom view.
      */
     public abstract View getCustomView();
@@ -790,6 +810,37 @@
          * Select this tab. Only valid if the tab has been added to the action bar.
          */
         public abstract void select();
+
+        /**
+         * Set a description of this tab's content for use in accessibility support.
+         * If no content description is provided the title will be used.
+         *
+         * @param resId A resource ID referring to the description text
+         * @return The current instance for call chaining
+         * @see #setContentDescription(CharSequence)
+         * @see #getContentDescription()
+         */
+        public abstract Tab setContentDescription(int resId);
+
+        /**
+         * Set a description of this tab's content for use in accessibility support.
+         * If no content description is provided the title will be used.
+         *
+         * @param contentDesc Description of this tab's content
+         * @return The current instance for call chaining
+         * @see #setContentDescription(int)
+         * @see #getContentDescription()
+         */
+        public abstract Tab setContentDescription(CharSequence contentDesc);
+
+        /**
+         * Gets a brief description of this tab's content for use in accessibility support.
+         *
+         * @return Description of this tab's content
+         * @see #setContentDescription(CharSequence)
+         * @see #setContentDescription(int)
+         */
+        public abstract CharSequence getContentDescription();
     }
 
     /**
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 5c641f1..b4e3988 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1021,6 +1021,13 @@
     public static final String FEATURE_WIFI = "android.hardware.wifi";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device supports Wi-Fi Direct networking.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct";
+
+    /**
      * Action to external storage service to clean out removed apps.
      * @hide
      */
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 63f2244..58f7869 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -3233,7 +3233,6 @@
          * captured pictures.
          *
          * @return true if video snapshot is supported.
-         * @hide
          */
         public boolean isVideoSnapshotSupported() {
             String str = get(KEY_VIDEO_SNAPSHOT_SUPPORTED);
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index 0411b5c..784bcc5 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -154,16 +154,16 @@
      * All values are in micro-Tesla (uT) and measure the ambient magnetic field
      * in the X, Y and Z axis.
      * 
-     * <h4>{@link android.hardware.Sensor#TYPE_GYROSCOPE Sensor.TYPE_GYROSCOPE}:</h4>
-     *  All values are in radians/second and measure the rate of rotation
-     *  around the X, Y and Z axis. The coordinate system is the same as is
-     *  used for the acceleration sensor.  Rotation is positive in the counter-clockwise
-     *  direction.  That is, an observer looking from some positive location on the x, y.
-     *  or z axis at a device positioned on the origin would report positive rotation
-     *  if the device appeared to be rotating counter clockwise.  Note that this is the
-     *  standard mathematical definition of positive rotation and does not agree with the
-     *  definition of roll given earlier.
-     *
+     * <h4>{@link android.hardware.Sensor#TYPE_GYROSCOPE Sensor.TYPE_GYROSCOPE}:
+     * </h4> All values are in radians/second and measure the rate of rotation
+     * around the device's local X, Y and Z axis. The coordinate system is the
+     * same as is used for the acceleration sensor. Rotation is positive in the
+     * counter-clockwise direction. That is, an observer looking from some
+     * positive location on the x, y or z axis at a device positioned on the
+     * origin would report positive rotation if the device appeared to be
+     * rotating counter clockwise. Note that this is the standard mathematical
+     * definition of positive rotation and does not agree with the definition of
+     * roll given earlier.
      * <ul>
      * <p>
      * values[0]: Angular speed around the x-axis
@@ -176,28 +176,61 @@
      * </p>
      * </ul>
      * <p>
-     * Typically the output of the gyroscope is integrated over time to calculate
-     * an angle, for example:
+     * Typically the output of the gyroscope is integrated over time to
+     * calculate a rotation describing the change of angles over the timestep,
+     * for example:
      * </p>
+     *
      * <pre class="prettyprint">
      *     private static final float NS2S = 1.0f / 1000000000.0f;
+     *     private final float[] deltaRotationVector = new float[4]();
      *     private float timestamp;
-     *     public void onSensorChanged(SensorEvent event)
-     *     {
+     *
+     *     public void onSensorChanged(SensorEvent event) {
+     *          // This timestep's delta rotation to be multiplied by the current rotation
+     *          // after computing it from the gyro sample data.
      *          if (timestamp != 0) {
      *              final float dT = (event.timestamp - timestamp) * NS2S;
-     *              angle[0] += event.values[0] * dT;
-     *              angle[1] += event.values[1] * dT;
-     *              angle[2] += event.values[2] * dT;
+     *              // Axis of the rotation sample, not normalized yet.
+     *              float axisX = event.values[0];
+     *              float axisY = event.values[1];
+     *              float axisZ = event.values[2];
+     *
+     *              // Calculate the angular speed of the sample
+     *              float omegaMagnitude = sqrt(axisX*axisX + axisY*axisY + axisZ*axisZ);
+     *
+     *              // Normalize the rotation vector if it's big enough to get the axis
+     *              if (omegaMagnitude > EPSILON) {
+     *                  axisX /= omegaMagnitude;
+     *                  axisY /= omegaMagnitude;
+     *                  axisZ /= omegaMagnitude;
+     *              }
+     *
+     *              // Integrate around this axis with the angular speed by the timestep
+     *              // in order to get a delta rotation from this sample over the timestep
+     *              // We will convert this axis-angle representation of the delta rotation
+     *              // into a quaternion before turning it into the rotation matrix.
+     *              float thetaOverTwo = omegaMagnitude * dT / 2.0f;
+     *              float sinThetaOverTwo = sin(thetaOverTwo);
+     *              float cosThetaOverTwo = cos(thetaOverTwo);
+     *              deltaRotationVector[0] = sinThetaOverTwo * axisX;
+     *              deltaRotationVector[1] = sinThetaOverTwo * axisY;
+     *              deltaRotationVector[2] = sinThetaOverTwo * axisZ;
+     *              deltaRotationVector[3] = cosThetaOverTwo;
      *          }
      *          timestamp = event.timestamp;
+     *          float[] deltaRotationMatrix = new float[9];
+     *          SensorManager.getRotationMatrixFromVector(deltaRotationMatrix, deltaRotationVector);
+     *          // User code should concatenate the delta rotation we computed with the current rotation
+     *          // in order to get the updated rotation.
+     *          // rotationCurrent = rotationCurrent * deltaRotationMatrix;
      *     }
      * </pre>
-     *
-     * <p>In practice, the gyroscope noise and offset will introduce some errors which need
-     * to be compensated for. This is usually done using the information from other
-     * sensors, but is beyond the scope of this document.</p>
-     *
+     * <p>
+     * In practice, the gyroscope noise and offset will introduce some errors
+     * which need to be compensated for. This is usually done using the
+     * information from other sensors, but is beyond the scope of this document.
+     * </p>
      * <h4>{@link android.hardware.Sensor#TYPE_LIGHT Sensor.TYPE_LIGHT}:</h4>
      * <ul>
      * <p>
diff --git a/core/java/android/inputmethodservice/ExtractEditText.java b/core/java/android/inputmethodservice/ExtractEditText.java
index 44e7e52..4fc63ed 100644
--- a/core/java/android/inputmethodservice/ExtractEditText.java
+++ b/core/java/android/inputmethodservice/ExtractEditText.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
 
 /***
@@ -142,4 +143,17 @@
     @Override public boolean hasFocus() {
         return this.isEnabled();
     }
+
+    /**
+     * @hide
+     */
+    @Override protected void viewClicked(InputMethodManager imm) {
+        // As an instance of this class is supposed to be owned by IMS,
+        // and it has a reference to the IMS (the current IME),
+        // we just need to call back its onViewClicked() here.
+        // It should be good to avoid unnecessary IPCs by doing this as well.
+        if (mIME != null) {
+            mIME.onViewClicked(false);
+        }
+    }
 }
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 370e22a..7d3cd92 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -618,7 +618,7 @@
         mTheme = Resources.selectSystemTheme(mTheme,
                 getApplicationInfo().targetSdkVersion,
                 android.R.style.Theme_InputMethod,
-                android.R.style.Theme_Holo,
+                android.R.style.Theme_Holo_InputMethod,
                 android.R.style.Theme_DeviceDefault_InputMethod);
         super.setTheme(mTheme);
         super.onCreate();
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 3918cfd..f3be39c 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -102,6 +102,21 @@
             this.txPackets = txPackets;
             this.operations = operations;
         }
+
+        @Override
+        public String toString() {
+            final StringBuilder builder = new StringBuilder();
+            builder.append("iface=").append(iface);
+            builder.append(" uid=").append(uid);
+            builder.append(" set=").append(setToString(set));
+            builder.append(" tag=").append(tagToString(tag));
+            builder.append(" rxBytes=").append(rxBytes);
+            builder.append(" rxPackets=").append(rxPackets);
+            builder.append(" txBytes=").append(txBytes);
+            builder.append(" txPackets=").append(txPackets);
+            builder.append(" operations=").append(operations);
+            return builder.toString();
+        }
     }
 
     public NetworkStats(long elapsedRealtime, int initialSize) {
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index a08ba2a..48739ba 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -260,6 +260,17 @@
             return old;
         }
 
+        private synchronized SpeechItem maybeRemoveCurrentSpeechItem(String callingApp) {
+            if (mCurrentSpeechItem != null &&
+                    TextUtils.equals(mCurrentSpeechItem.getCallingApp(), callingApp)) {
+                SpeechItem current = mCurrentSpeechItem;
+                mCurrentSpeechItem = null;
+                return current;
+            }
+
+            return null;
+        }
+
         public boolean isSpeaking() {
             return getCurrentSpeechItem() != null;
         }
@@ -287,14 +298,9 @@
             }
 
             if (queueMode == TextToSpeech.QUEUE_FLUSH) {
-                stop(speechItem.getCallingApp());
+                stopForApp(speechItem.getCallingApp());
             } else if (queueMode == TextToSpeech.QUEUE_DESTROY) {
-                // Stop the current speech item.
-                stop(speechItem.getCallingApp());
-                // Remove all other items from the queue.
-                removeCallbacksAndMessages(null);
-                // Remove all pending playback as well.
-                mAudioPlaybackHandler.removeAllItems();
+                stopAll();
             }
             Runnable runnable = new Runnable() {
                 @Override
@@ -305,7 +311,8 @@
                 }
             };
             Message msg = Message.obtain(this, runnable);
-            // The obj is used to remove all callbacks from the given app in stop(String).
+            // The obj is used to remove all callbacks from the given app in
+            // stopForApp(String).
             //
             // Note that this string is interned, so the == comparison works.
             msg.obj = speechItem.getCallingApp();
@@ -323,7 +330,7 @@
          *
          * Called on a service binder thread.
          */
-        public int stop(String callingApp) {
+        public int stopForApp(String callingApp) {
             if (TextUtils.isEmpty(callingApp)) {
                 return TextToSpeech.ERROR;
             }
@@ -331,8 +338,13 @@
             removeCallbacksAndMessages(callingApp);
             // This stops writing data to the file / or publishing
             // items to the audio playback handler.
-            SpeechItem current = setCurrentSpeechItem(null);
-            if (current != null && TextUtils.equals(callingApp, current.getCallingApp())) {
+            //
+            // Note that the current speech item must be removed only if it
+            // belongs to the callingApp, else the item will be "orphaned" and
+            // not stopped correctly if a stop request comes along for the item
+            // from the app it belongs to.
+            SpeechItem current = maybeRemoveCurrentSpeechItem(callingApp);
+            if (current != null) {
                 current.stop();
             }
 
@@ -341,6 +353,20 @@
 
             return TextToSpeech.SUCCESS;
         }
+
+        public int stopAll() {
+            // Stop the current speech item unconditionally.
+            SpeechItem current = setCurrentSpeechItem(null);
+            if (current != null) {
+                current.stop();
+            }
+            // Remove all other items from the queue.
+            removeCallbacksAndMessages(null);
+            // Remove all pending playback as well.
+            mAudioPlaybackHandler.removeAllItems();
+
+            return TextToSpeech.SUCCESS;
+        }
     }
 
     interface UtteranceCompletedDispatcher {
@@ -412,6 +438,10 @@
             }
         }
 
+        protected synchronized boolean isStopped() {
+             return mStopped;
+        }
+
         protected abstract int playImpl();
 
         protected abstract void stopImpl();
@@ -473,7 +503,7 @@
                 Log.w(TAG, "Got empty text");
                 return false;
             }
-            if (mText.length() >= MAX_SPEECH_ITEM_CHAR_LENGTH){
+            if (mText.length() >= MAX_SPEECH_ITEM_CHAR_LENGTH) {
                 Log.w(TAG, "Text too long: " + mText.length() + " chars");
                 return false;
             }
@@ -485,6 +515,11 @@
             AbstractSynthesisCallback synthesisCallback;
             mEventLogger.onRequestProcessingStart();
             synchronized (this) {
+                // stop() might have been called before we enter this
+                // synchronized block.
+                if (isStopped()) {
+                    return TextToSpeech.ERROR;
+                }
                 mSynthesisCallback = createSynthesisCallback();
                 synthesisCallback = mSynthesisCallback;
             }
@@ -510,8 +545,13 @@
             synchronized (this) {
                 synthesisCallback = mSynthesisCallback;
             }
-            synthesisCallback.stop();
-            TextToSpeechService.this.onStop();
+            if (synthesisCallback != null) {
+                // If the synthesis callback is null, it implies that we haven't
+                // entered the synchronized(this) block in playImpl which in
+                // turn implies that synthesis would not have started.
+                synthesisCallback.stop();
+                TextToSpeechService.this.onStop();
+            }
         }
 
         public String getLanguage() {
@@ -719,7 +759,7 @@
                 return TextToSpeech.ERROR;
             }
 
-            return mSynthHandler.stop(intern(callingApp));
+            return mSynthHandler.stopForApp(intern(callingApp));
         }
 
         public String[] getLanguage() {
@@ -811,7 +851,7 @@
             synchronized (mAppToCallback) {
                 mAppToCallback.remove(packageName);
             }
-            mSynthHandler.stop(packageName);
+            mSynthHandler.stopForApp(packageName);
         }
 
         @Override
diff --git a/core/java/android/text/TextPaint.java b/core/java/android/text/TextPaint.java
index 625d869..afd9892 100644
--- a/core/java/android/text/TextPaint.java
+++ b/core/java/android/text/TextPaint.java
@@ -72,8 +72,15 @@
         linkColor = tp.linkColor;
         drawableState = tp.drawableState;
         density = tp.density;
-        underlineColors = tp.underlineColors;
-        underlineThicknesses = tp.underlineThicknesses;
+
+        if (tp.underlineColors != null) {
+            if (underlineColors == null || underlineColors.length < tp.underlineCount) {
+                underlineColors = new int[tp.underlineCount];
+                underlineThicknesses = new float[tp.underlineCount];
+            }
+            System.arraycopy(tp.underlineColors, 0, underlineColors, 0, tp.underlineCount);
+            System.arraycopy(tp.underlineThicknesses, 0, underlineThicknesses, 0, tp.underlineCount);
+        }
         underlineCount = tp.underlineCount;
     }
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ba23218..fd60813f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -636,7 +636,8 @@
  *
  * @see android.view.ViewGroup
  */
-public class View implements Drawable.Callback2, KeyEvent.Callback, AccessibilityEventSource {
+public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Callback,
+        AccessibilityEventSource {
     private static final boolean DBG = false;
 
     /**
@@ -2583,6 +2584,8 @@
 
     /**
      * Default text direction is inherited
+     *
+     * @hide
      */
     protected static int DEFAULT_TEXT_DIRECTION = TEXT_DIRECTION_INHERIT;
 
@@ -9294,6 +9297,11 @@
         recomputePadding();
     }
 
+    /**
+     * Return true if layout direction resolution can be done
+     *
+     * @hide
+     */
     protected boolean canResolveLayoutDirection() {
         switch (getLayoutDirection()) {
             case LAYOUT_DIRECTION_INHERIT:
@@ -9322,6 +9330,8 @@
      *
      * @param locale Locale to check
      * @return true if a Locale is corresponding to a RTL script.
+     *
+     * @hide
      */
     protected static boolean isLayoutDirectionRtl(Locale locale) {
         return (LocaleUtil.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE ==
@@ -13240,7 +13250,6 @@
      * {@link #TEXT_DIRECTION_INHERIT},
      * {@link #TEXT_DIRECTION_FIRST_STRONG}
      * {@link #TEXT_DIRECTION_ANY_RTL},
-     * {@link #TEXT_DIRECTION_CHAR_COUNT},
      * {@link #TEXT_DIRECTION_LTR},
      * {@link #TEXT_DIRECTION_RTL},
      *
@@ -13258,7 +13267,6 @@
      * {@link #TEXT_DIRECTION_INHERIT},
      * {@link #TEXT_DIRECTION_FIRST_STRONG}
      * {@link #TEXT_DIRECTION_ANY_RTL},
-     * {@link #TEXT_DIRECTION_CHAR_COUNT},
      * {@link #TEXT_DIRECTION_LTR},
      * {@link #TEXT_DIRECTION_RTL},
      *
@@ -13279,7 +13287,6 @@
      *
      * {@link #TEXT_DIRECTION_FIRST_STRONG}
      * {@link #TEXT_DIRECTION_ANY_RTL},
-     * {@link #TEXT_DIRECTION_CHAR_COUNT},
      * {@link #TEXT_DIRECTION_LTR},
      * {@link #TEXT_DIRECTION_RTL},
      *
@@ -13294,6 +13301,8 @@
 
     /**
      * Resolve the text direction.
+     *
+     * @hide
      */
     protected void resolveTextDirection() {
         if (mTextDirection != TEXT_DIRECTION_INHERIT) {
@@ -13309,6 +13318,8 @@
 
     /**
      * Reset resolved text direction. Will be resolved during a call to getResolvedTextDirection().
+     *
+     * @hide
      */
     protected void resetResolvedTextDirection() {
         mResolvedTextDirection = TEXT_DIRECTION_INHERIT;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index eedf5e2..fb3f6e8 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1619,11 +1619,13 @@
                 }
             }
         } else {
-            // If we're not drawing, then we don't need to draw the transitions, either
-            if (mPendingTransitions != null) {
+            // End any pending transitions on this non-visible window
+            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
+                for (int i = 0; i < mPendingTransitions.size(); ++i) {
+                    mPendingTransitions.get(i).endChangingAnimations();
+                }
                 mPendingTransitions.clear();
             }
-
             // We were supposed to report when we are done drawing. Since we canceled the
             // draw, remember it here.
             if ((relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) {
@@ -1632,7 +1634,7 @@
             if (fullRedrawNeeded) {
                 mFullRedrawNeeded = true;
             }
-            
+
             if (viewVisibility == View.VISIBLE) {
                 // Try again
                 scheduleTraversals();
@@ -4541,7 +4543,7 @@
                 predicate.init(accessibilityId);
                 View root = ViewRootImpl.this.mView;
                 View target = root.findViewByPredicate(predicate);
-                if (target != null && target.isShown()) {
+                if (target != null && target.getVisibility() == View.VISIBLE) {
                     info = target.createAccessibilityNodeInfo();
                 }
             } finally {
@@ -4584,7 +4586,7 @@
             try {
                 View root = ViewRootImpl.this.mView;
                 View target = root.findViewById(viewId);
-                if (target != null && target.isShown()) {
+                if (target != null && target.getVisibility() == View.VISIBLE) {
                     info = target.createAccessibilityNodeInfo();
                 }
             } finally {
@@ -4635,14 +4637,14 @@
                 ArrayList<View> foundViews = mAttachInfo.mFocusablesTempList;
                 foundViews.clear();
 
-                View root;
+                View root = null;
                 if (accessibilityViewId != View.NO_ID) {
                     root = findViewByAccessibilityId(accessibilityViewId);
                 } else {
                     root = ViewRootImpl.this.mView;
                 }
 
-                if (root == null || !root.isShown()) {
+                if (root == null || root.getVisibility() != View.VISIBLE) {
                     return;
                 }
 
@@ -4657,7 +4659,7 @@
                 final int viewCount = foundViews.size();
                 for (int i = 0; i < viewCount; i++) {
                     View foundView = foundViews.get(i);
-                    if (foundView.isShown()) {
+                    if (foundView.getVisibility() == View.VISIBLE) {
                         infos.add(foundView.createAccessibilityNodeInfo());
                     }
                  }
@@ -4730,7 +4732,7 @@
 
         private boolean performActionFocus(int accessibilityId) {
             View target = findViewByAccessibilityId(accessibilityId);
-            if (target == null) {
+            if (target == null || target.getVisibility() != View.VISIBLE) {
                 return false;
             }
             // Get out of touch mode since accessibility wants to move focus around.
@@ -4740,7 +4742,7 @@
 
         private boolean performActionClearFocus(int accessibilityId) {
             View target = findViewByAccessibilityId(accessibilityId);
-            if (target == null) {
+            if (target == null || target.getVisibility() != View.VISIBLE) {
                 return false;
             }
             if (!target.isFocused()) {
@@ -4752,7 +4754,7 @@
 
         private boolean performActionSelect(int accessibilityId) {
             View target = findViewByAccessibilityId(accessibilityId);
-            if (target == null) {
+            if (target == null || target.getVisibility() != View.VISIBLE) {
                 return false;
             }
             if (target.isSelected()) {
@@ -4764,7 +4766,7 @@
 
         private boolean performActionClearSelection(int accessibilityId) {
             View target = findViewByAccessibilityId(accessibilityId);
-            if (target == null) {
+            if (target == null || target.getVisibility() != View.VISIBLE) {
                 return false;
             }
             if (!target.isSelected()) {
@@ -4781,18 +4783,21 @@
             }
             mFindByAccessibilityIdPredicate.init(accessibilityId);
             View foundView = root.findViewByPredicate(mFindByAccessibilityIdPredicate);
-            return (foundView != null && foundView.isShown()) ? foundView : null;
+            if (foundView == null || foundView.getVisibility() != View.VISIBLE) {
+                return null;
+            }
+            return foundView;
         }
 
         private final class FindByAccessibilitytIdPredicate implements Predicate<View> {
-            public int mSerchedId;
+            public int mSearchedId;
 
             public void init(int searchedId) {
-                mSerchedId = searchedId;
+                mSearchedId = searchedId;
             }
 
             public boolean apply(View view) {
-                return (view.getAccessibilityViewId() == mSerchedId);
+                return (view.getAccessibilityViewId() == mSearchedId);
             }
         }
     }
diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java
index 4f2542b..58373bc 100644
--- a/core/java/android/view/animation/AnimationSet.java
+++ b/core/java/android/view/animation/AnimationSet.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.os.Build;
 import android.util.AttributeSet;
 import android.graphics.RectF;
 
@@ -31,6 +32,22 @@
  * If AnimationSet sets any properties that its children also set
  * (for example, duration or fillBefore), the values of AnimationSet
  * override the child values.
+ *
+ * <p>The way that AnimationSet inherits behavior from Animation is important to
+ * understand. Some of the Animation attributes applied to AnimationSet affect the
+ * AnimationSet itself, some are pushed down to the children, and some are ignored,
+ * as follows:
+ * <ul>
+ *     <li>duration, repeatMode, fillBefore, fillAfter: These properties, when set
+ *     on an AnimationSet object, will be pushed down to all child animations.</li>
+ *     <li>repeatCount, fillEnabled: These properties are ignored for AnimationSet.</li>
+ *     <li>startOffset, shareInterpolator: These properties apply to the AnimationSet itself.</li>
+ * </ul>
+ * Starting with {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH},
+ * the behavior of these properties is the same in XML resources and at runtime (prior to that
+ * release, the values set in XML were ignored for AnimationSet). That is, calling
+ * <code>setDuration(500)</code> on an AnimationSet has the same effect as declaring
+ * <code>android:duration="500"</code> in an XML resource for an AnimationSet object.</p>
  */
 public class AnimationSet extends Animation {
     private static final int PROPERTY_FILL_AFTER_MASK         = 0x1;
@@ -69,7 +86,26 @@
         setFlag(PROPERTY_SHARE_INTERPOLATOR_MASK,
                 a.getBoolean(com.android.internal.R.styleable.AnimationSet_shareInterpolator, true));
         init();
-        
+
+        if (context.getApplicationInfo().targetSdkVersion >=
+                Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+            if (a.hasValue(com.android.internal.R.styleable.Animation_duration)) {
+                mFlags |= PROPERTY_DURATION_MASK;
+            }
+            if (a.hasValue(com.android.internal.R.styleable.Animation_fillBefore)) {
+                mFlags |= PROPERTY_FILL_BEFORE_MASK;
+            }
+            if (a.hasValue(com.android.internal.R.styleable.Animation_fillAfter)) {
+                mFlags |= PROPERTY_FILL_AFTER_MASK;
+            }
+            if (a.hasValue(com.android.internal.R.styleable.Animation_repeatMode)) {
+                mFlags |= PROPERTY_REPEAT_MODE_MASK;
+            }
+            if (a.hasValue(com.android.internal.R.styleable.Animation_startOffset)) {
+                mFlags |= PROPERTY_START_OFFSET_MASK;
+            }
+        }
+
         a.recycle();
     }
     
@@ -112,7 +148,6 @@
 
     private void init() {
         mStartTime = 0;
-        mDuration = 0;
     }
 
     @Override
@@ -171,6 +206,7 @@
     public void setDuration(long durationMillis) {
         mFlags |= PROPERTY_DURATION_MASK;
         super.setDuration(durationMillis);
+        mLastEnd = mStartOffset + mDuration;
     }
 
     /**
@@ -192,12 +228,16 @@
             mFlags |= PROPERTY_CHANGE_BOUNDS_MASK;
         }
 
-        if (mAnimations.size() == 1) {
-            mDuration = a.getStartOffset() + a.getDuration();
+        if ((mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK) {
             mLastEnd = mStartOffset + mDuration;
         } else {
-            mLastEnd = Math.max(mLastEnd, a.getStartOffset() + a.getDuration());
-            mDuration = mLastEnd - mStartOffset;
+            if (mAnimations.size() == 1) {
+                mDuration = a.getStartOffset() + a.getDuration();
+                mLastEnd = mStartOffset + mDuration;
+            } else {
+                mLastEnd = Math.max(mLastEnd, a.getStartOffset() + a.getDuration());
+                mDuration = mLastEnd - mStartOffset;
+            }
         }
 
         mDirty = true;
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index f89d490..49b4f66 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -43,6 +43,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.ref.WeakReference;
+import java.net.URL;
 import java.net.URLEncoder;
 import java.nio.charset.Charsets;
 import java.security.PrivateKey;
@@ -1171,7 +1172,8 @@
             X509Certificate cert = new X509CertImpl(cert_der);
             SslCertificate sslCert = new SslCertificate(cert);
             if (JniUtil.useChromiumHttpStack()) {
-                ssl_error = SslError.SslErrorFromChromiumErrorCode(cert_error, sslCert, url);
+                ssl_error = SslError.SslErrorFromChromiumErrorCode(cert_error, sslCert,
+                        new URL(url).getHost());
             } else {
                 ssl_error = new SslError(cert_error, cert, url);
             }
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 5bb0ef2..7ba93da0 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -20,6 +20,7 @@
 import android.app.AlertDialog;
 import android.content.BroadcastReceiver;
 import android.content.ClipboardManager;
+import android.content.ComponentCallbacks2;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnCancelListener;
@@ -382,6 +383,39 @@
         }
     }
 
+    private static class OnTrimMemoryListener implements ComponentCallbacks2 {
+        private static OnTrimMemoryListener sInstance = null;
+
+        static void init(Context c) {
+            if (sInstance == null) {
+                sInstance = new OnTrimMemoryListener(c.getApplicationContext());
+            }
+        }
+
+        private OnTrimMemoryListener(Context c) {
+            c.registerComponentCallbacks(this);
+        }
+
+        @Override
+        public void onConfigurationChanged(Configuration newConfig) {
+            // Ignore
+        }
+
+        @Override
+        public void onLowMemory() {
+            // Ignore
+        }
+
+        @Override
+        public void onTrimMemory(int level) {
+            if (DebugFlags.WEB_VIEW) {
+                Log.d("WebView", "onTrimMemory: " + level);
+            }
+            WebView.nativeOnTrimMemory(level);
+        }
+
+    }
+
     // A final CallbackProxy shared by WebViewCore and BrowserFrame.
     private final CallbackProxy mCallbackProxy;
 
@@ -1194,6 +1228,8 @@
     }
 
     private void init() {
+        OnTrimMemoryListener.init(getContext());
+
         setWillNotDraw(false);
         setFocusable(true);
         setFocusableInTouchMode(true);
@@ -9404,4 +9440,8 @@
     native boolean  nativeSetProperty(String key, String value);
     native String   nativeGetProperty(String key);
     private native void     nativeGetTextSelectionRegion(Region region);
+    /**
+     * See {@link ComponentCallbacks2} for the trim levels and descriptions
+     */
+    private static native void     nativeOnTrimMemory(int level);
 }
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index b945038..2d10bbe 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -886,9 +886,11 @@
             event.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED);
         }
 
-        // We first get a chance to populate the event.
-        onPopulateAccessibilityEvent(event);
-
+        View selectedView = getSelectedView();
+        if (selectedView != null && selectedView.getVisibility() == VISIBLE) {
+            // We first get a chance to populate the event.
+            onPopulateAccessibilityEvent(event);
+        }
         return false;
     }
 
@@ -896,10 +898,7 @@
     public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
         // We send selection events only from AdapterView to avoid
         // generation of such event for each child.
-        View selectedView = getSelectedView();
-        if (selectedView != null) {
-            selectedView.dispatchPopulateAccessibilityEvent(event);
-        }
+        getSelectedView().dispatchPopulateAccessibilityEvent(event);
     }
 
     @Override
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index a5cf62e..6edfd59 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -961,7 +961,8 @@
         }
 
         for (View view : mTopToBottomLeftToRightSet) {
-            if (view.dispatchPopulateAccessibilityEvent(event)) {
+            if (view.getVisibility() == View.VISIBLE
+                    && view.dispatchPopulateAccessibilityEvent(event)) {
                 mTopToBottomLeftToRightSet.clear();
                 return true;
             }
diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java
index 4b08f2d..0cd14d0 100644
--- a/core/java/android/widget/StackView.java
+++ b/core/java/android/widget/StackView.java
@@ -148,14 +148,23 @@
     private int mFramePadding;
     private final Rect stackInvalidateRect = new Rect();
 
+    /**
+     * {@inheritDoc}
+     */
     public StackView(Context context) {
         this(context, null);
     }
 
+    /**
+     * {@inheritDoc}
+     */
     public StackView(Context context, AttributeSet attrs) {
         this(context, attrs, com.android.internal.R.attr.stackViewStyle);
     }
 
+    /**
+     * {@inheritDoc}
+     */
     public StackView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         TypedArray a = context.obtainStyledAttributes(attrs,
diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java
index 9afb625..191c4ca 100644
--- a/core/java/android/widget/TabWidget.java
+++ b/core/java/android/widget/TabWidget.java
@@ -405,7 +405,10 @@
         onPopulateAccessibilityEvent(event);
         // Dispatch only to the selected tab.
         if (mSelectedTab != -1) {
-            return getChildTabViewAt(mSelectedTab).dispatchPopulateAccessibilityEvent(event);
+            View tabView = getChildTabViewAt(mSelectedTab);
+            if (tabView != null && tabView.getVisibility() == VISIBLE) {
+                return tabView.dispatchPopulateAccessibilityEvent(event);
+            }
         }
         return false;
     }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 7f410aa..d88999b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -5432,8 +5432,8 @@
                         if (mMovement != null && mText instanceof Editable
                                 && mLayout != null && onCheckIsTextEditor()) {
                             InputMethodManager imm = InputMethodManager.peekInstance();
+                            viewClicked(imm);
                             if (imm != null) {
-                                imm.viewClicked(this);
                                 imm.showSoftInput(this, 0);
                             }
                         }
@@ -8346,9 +8346,7 @@
             if (touchIsFinished && (isTextEditable() || mTextIsSelectable)) {
                 // Show the IME, except when selecting in read-only text.
                 final InputMethodManager imm = InputMethodManager.peekInstance();
-                if (imm != null) {
-                    imm.viewClicked(this);
-                }
+                viewClicked(imm);
                 if (!mTextIsSelectable) {
                     handled |= imm != null && imm.showSoftInput(this, 0);
                 }
@@ -9555,6 +9553,8 @@
             public void dismiss() {
                 super.dismiss();
 
+                TextView.this.getPositionListener().removeSubscriber(SuggestionsPopupWindow.this);
+
                 if ((mText instanceof Editable) && mSuggestionRangeSpan != null) {
                     ((Editable) mText).removeSpan(mSuggestionRangeSpan);
                 }
@@ -11308,6 +11308,15 @@
         mResolvedDrawables = false;
     }
 
+    /**
+     * @hide
+     */
+    protected void viewClicked(InputMethodManager imm) {
+        if (imm != null) {
+            imm.viewClicked(this);
+        }
+    }
+
     @ViewDebug.ExportedProperty(category = "text")
     private CharSequence            mText;
     private CharSequence            mTransformed;
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
index cfecca5..ccca22e 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -346,7 +346,17 @@
     }
 
     public void setBackgroundDrawable(Drawable d) {
-        mContainerView.setBackgroundDrawable(d);
+        mContainerView.setPrimaryBackground(d);
+    }
+
+    public void setStackedBackgroundDrawable(Drawable d) {
+        mContainerView.setStackedBackground(d);
+    }
+
+    public void setSplitBackgroundDrawable(Drawable d) {
+        if (mSplitView != null) {
+            mSplitView.setSplitBackground(d);
+        }
     }
 
     public View getCustomView() {
@@ -783,6 +793,7 @@
         private Object mTag;
         private Drawable mIcon;
         private CharSequence mText;
+        private CharSequence mContentDesc;
         private int mPosition = -1;
         private View mCustomView;
 
@@ -878,6 +889,25 @@
         public void select() {
             selectTab(this);
         }
+
+        @Override
+        public Tab setContentDescription(int resId) {
+            return setContentDescription(mContext.getResources().getText(resId));
+        }
+
+        @Override
+        public Tab setContentDescription(CharSequence contentDesc) {
+            mContentDesc = contentDesc;
+            if (mPosition >= 0) {
+                mTabScrollView.updateTab(mPosition);
+            }
+            return this;
+        }
+
+        @Override
+        public CharSequence getContentDescription() {
+            return mContentDesc;
+        }
     }
 
     @Override
diff --git a/core/java/com/android/internal/backup/BackupConstants.java b/core/java/com/android/internal/backup/BackupConstants.java
index 3ee11bd..906b5d5 100644
--- a/core/java/com/android/internal/backup/BackupConstants.java
+++ b/core/java/com/android/internal/backup/BackupConstants.java
@@ -23,4 +23,5 @@
     public static final int TRANSPORT_OK = 0;
     public static final int TRANSPORT_ERROR = 1;
     public static final int TRANSPORT_NOT_INITIALIZED = 2;
+    public static final int AGENT_ERROR = 3;
 }
diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java
index fd9ee08..f95de62 100644
--- a/core/java/com/android/internal/widget/ActionBarContainer.java
+++ b/core/java/com/android/internal/widget/ActionBarContainer.java
@@ -76,6 +76,21 @@
         mActionBarView = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
     }
 
+    public void setPrimaryBackground(Drawable bg) {
+        mBackground = bg;
+        invalidate();
+    }
+
+    public void setStackedBackground(Drawable bg) {
+        mStackedBackground = bg;
+        invalidate();
+    }
+
+    public void setSplitBackground(Drawable bg) {
+        mSplitBackground = bg;
+        invalidate();
+    }
+
     /**
      * Set the action bar into a "transitioning" state. While transitioning
      * the bar will block focus and touch from all of its descendants. This
diff --git a/core/java/com/android/internal/widget/ScrollingTabContainerView.java b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
index 71f9364..5baed75 100644
--- a/core/java/com/android/internal/widget/ScrollingTabContainerView.java
+++ b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
@@ -443,6 +443,8 @@
                     mTextView.setVisibility(GONE);
                     mTextView.setText(null);
                 }
+
+                setContentDescription(tab.getContentDescription());
             }
         }
 
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index b01dcd8..9313d0a 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -760,7 +760,8 @@
         jint count = end - start;
         sp<TextLayoutCacheValue> value;
 #if USE_TEXT_LAYOUT_CACHE
-        value = gTextLayoutCache.getValue(paint, textArray, start, count, end, flags);
+        value = TextLayoutCache::getInstance().getValue(paint, textArray, start, count,
+                        end, flags);
         if (value == NULL) {
             LOGE("Cannot get TextLayoutCache value");
             return ;
@@ -780,7 +781,8 @@
 
         sp<TextLayoutCacheValue> value;
 #if USE_TEXT_LAYOUT_CACHE
-        value = gTextLayoutCache.getValue(paint, textArray, start, count, contextCount, flags);
+        value = TextLayoutCache::getInstance().getValue(paint, textArray, start, count,
+                        contextCount, flags);
         if (value == NULL) {
             LOGE("Cannot get TextLayoutCache value");
             return ;
diff --git a/core/jni/android/graphics/TextLayout.cpp b/core/jni/android/graphics/TextLayout.cpp
index 7e89a37..fa9a7b7 100644
--- a/core/jni/android/graphics/TextLayout.cpp
+++ b/core/jni/android/graphics/TextLayout.cpp
@@ -257,7 +257,7 @@
     sp<TextLayoutCacheValue> value;
 #if USE_TEXT_LAYOUT_CACHE
     // Return advances from the cache. Compute them if needed
-    value = gTextLayoutCache.getValue(
+    value = TextLayoutCache::getInstance().getValue(
             paint, chars, start, count, contextCount, dirFlags);
 #else
     value = new TextLayoutCacheValue();
diff --git a/core/jni/android/graphics/TextLayout.h b/core/jni/android/graphics/TextLayout.h
index 9bb1b92..0a29d78 100644
--- a/core/jni/android/graphics/TextLayout.h
+++ b/core/jni/android/graphics/TextLayout.h
@@ -41,11 +41,6 @@
  */
 #define USE_TEXT_LAYOUT_CACHE 1
 
-
-#if USE_TEXT_LAYOUT_CACHE
-    static TextLayoutCache gTextLayoutCache;
-#endif
-
 enum {
     kBidi_LTR = 0,
     kBidi_RTL = 1,
diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp
index f04c5eb5..7f79277 100644
--- a/core/jni/android/graphics/TextLayoutCache.cpp
+++ b/core/jni/android/graphics/TextLayoutCache.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#define LOG_TAG "TextLayoutCache"
+
 #include "TextLayoutCache.h"
 #include "TextLayout.h"
 
@@ -23,6 +25,12 @@
 
 namespace android {
 
+//--------------------------------------------------------------------------------------------------
+#if USE_TEXT_LAYOUT_CACHE
+    ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutCache);
+#endif
+//--------------------------------------------------------------------------------------------------
+
 TextLayoutCache::TextLayoutCache() :
         mCache(GenerationCache<TextLayoutCacheKey, sp<TextLayoutCacheValue> >::kUnlimitedCapacity),
         mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)),
@@ -30,13 +38,6 @@
     init();
 }
 
-TextLayoutCache::TextLayoutCache(uint32_t max):
-        mCache(GenerationCache<TextLayoutCacheKey, sp<TextLayoutCacheValue> >::kUnlimitedCapacity),
-        mSize(0), mMaxSize(max),
-        mCacheHitCount(0), mNanosecondsSaved(0) {
-    init();
-}
-
 TextLayoutCache::~TextLayoutCache() {
     mCache.clear();
 }
@@ -46,25 +47,21 @@
 
     mDebugLevel = readRtlDebugLevel();
     mDebugEnabled = mDebugLevel & kRtlDebugCaches;
-    LOGD("Using TextLayoutCache debug level: %d - Debug Enabled: %d", mDebugLevel, mDebugEnabled);
+    LOGD("Using debug level: %d - Debug Enabled: %d", mDebugLevel, mDebugEnabled);
 
     mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
-    if (mDebugEnabled) {
-        LOGD("TextLayoutCache start time: %lld", mCacheStartTime);
-    }
-    mInitialized = true;
 
     if (mDebugEnabled) {
+        LOGD("Start time: %lld", mCacheStartTime);
 #if RTL_USE_HARFBUZZ
-        LOGD("TextLayoutCache is using HARFBUZZ");
+        LOGD("Using HARFBUZZ");
 #else
-        LOGD("TextLayoutCache is using ICU");
+        LOGD("Using ICU");
 #endif
+        LOGD("Initialization is done");
     }
 
-    if (mDebugEnabled) {
-        LOGD("TextLayoutCache initialization is done");
-    }
+    mInitialized = true;
 }
 
 /*
@@ -147,8 +144,7 @@
             // Cleanup to make some room if needed
             if (mSize + size > mMaxSize) {
                 if (mDebugEnabled) {
-                    LOGD("TextLayoutCache: need to clean some entries "
-                            "for making some room for a new entry");
+                    LOGD("Need to clean some entries for making some room for a new entry");
                 }
                 while (mSize + size > mMaxSize) {
                     // This will call the callback
@@ -213,7 +209,7 @@
     float remainingPercent = 100 * ((mMaxSize - mSize) / ((float)mMaxSize));
     float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000;
     LOGD("------------------------------------------------");
-    LOGD("TextLayoutCache stats");
+    LOGD("Cache stats");
     LOGD("------------------------------------------------");
     LOGD("pid       : %d", getpid());
     LOGD("running   : %.0f seconds", timeRunningInSec);
diff --git a/core/jni/android/graphics/TextLayoutCache.h b/core/jni/android/graphics/TextLayoutCache.h
index 10dee87..0d8d71f 100644
--- a/core/jni/android/graphics/TextLayoutCache.h
+++ b/core/jni/android/graphics/TextLayoutCache.h
@@ -25,6 +25,7 @@
 #include <utils/GenerationCache.h>
 #include <utils/Compare.h>
 #include <utils/RefBase.h>
+#include <utils/Singleton.h>
 
 #include <SkPaint.h>
 #include <SkTemplates.h>
@@ -187,11 +188,11 @@
 /**
  * Cache of text layout information.
  */
-class TextLayoutCache : public OnEntryRemoved<TextLayoutCacheKey, sp<TextLayoutCacheValue> >
+class TextLayoutCache : public OnEntryRemoved<TextLayoutCacheKey, sp<TextLayoutCacheValue> >,
+        public Singleton<TextLayoutCache>
 {
 public:
     TextLayoutCache();
-    TextLayoutCache(uint32_t maxByteSize);
 
     virtual ~TextLayoutCache();
 
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index bcf8e71..395e417 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -477,7 +477,7 @@
 #if RTL_USE_HARFBUZZ
     sp<TextLayoutCacheValue> value;
 #if USE_TEXT_LAYOUT_CACHE
-    value = gTextLayoutCache.getValue(paint, text, 0, count, count, flags);
+    value = TextLayoutCache::getInstance().getValue(paint, text, 0, count, count, flags);
     if (value == NULL) {
         LOGE("Cannot get TextLayoutCache value");
         return ;
@@ -507,7 +507,7 @@
 #if RTL_USE_HARFBUZZ
     sp<TextLayoutCacheValue> value;
 #if USE_TEXT_LAYOUT_CACHE
-    value = gTextLayoutCache.getValue(paint, text, start, count, contextCount, flags);
+    value = TextLayoutCache::getInstance().getValue(paint, text, start, count, contextCount, flags);
     if (value == NULL) {
         LOGE("Cannot get TextLayoutCache value");
         return ;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3fa4d48..16490da 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -87,9 +87,7 @@
     <protected-broadcast android:name="android.bluetooth.device.action.NAME_FAILED" />
     <protected-broadcast android:name="android.bluetooth.device.action.PAIRING_REQUEST" />
     <protected-broadcast android:name="android.bluetooth.device.action.PAIRING_CANCEL" />
-    <protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST" />
     <protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REPLY" />
-    <protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL" />
     <protected-broadcast
         android:name="android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast
diff --git a/core/res/res/drawable-hdpi/list_divider_holo_dark.9.png b/core/res/res/drawable-hdpi/list_divider_holo_dark.9.png
deleted file mode 100644
index 7264a7a..0000000
--- a/core/res/res/drawable-hdpi/list_divider_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_divider_holo_light.9.png b/core/res/res/drawable-hdpi/list_divider_holo_light.9.png
deleted file mode 100644
index 74f48f5..0000000
--- a/core/res/res/drawable-hdpi/list_divider_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_longpressed_holo.9.png b/core/res/res/drawable-hdpi/list_longpressed_holo.9.png
index d06549c..4ea7afa 100644
--- a/core/res/res/drawable-hdpi/list_longpressed_holo.9.png
+++ b/core/res/res/drawable-hdpi/list_longpressed_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_selected_holo_dark.9.png b/core/res/res/drawable-hdpi/list_selected_holo_dark.9.png
index dae40ca..e20b02d 100644
--- a/core/res/res/drawable-hdpi/list_selected_holo_dark.9.png
+++ b/core/res/res/drawable-hdpi/list_selected_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/list_selected_holo_light.9.png b/core/res/res/drawable-hdpi/list_selected_holo_light.9.png
index dae40ca..e20b02d 100644
--- a/core/res/res/drawable-hdpi/list_selected_holo_light.9.png
+++ b/core/res/res/drawable-hdpi/list_selected_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_divider_holo_dark.9.png b/core/res/res/drawable-mdpi/list_divider_holo_dark.9.png
deleted file mode 100644
index aa8015d..0000000
--- a/core/res/res/drawable-mdpi/list_divider_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_divider_holo_light.9.png b/core/res/res/drawable-mdpi/list_divider_holo_light.9.png
deleted file mode 100644
index c25c256..0000000
--- a/core/res/res/drawable-mdpi/list_divider_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_longpressed_holo.9.png b/core/res/res/drawable-mdpi/list_longpressed_holo.9.png
index 2b8a0b3..3bf8e03 100644
--- a/core/res/res/drawable-mdpi/list_longpressed_holo.9.png
+++ b/core/res/res/drawable-mdpi/list_longpressed_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_selected_holo_dark.9.png b/core/res/res/drawable-mdpi/list_selected_holo_dark.9.png
index 4cbcee9..13cb131 100644
--- a/core/res/res/drawable-mdpi/list_selected_holo_dark.9.png
+++ b/core/res/res/drawable-mdpi/list_selected_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/list_selected_holo_light.9.png b/core/res/res/drawable-mdpi/list_selected_holo_light.9.png
index 4cbcee9..13cb131 100644
--- a/core/res/res/drawable-mdpi/list_selected_holo_light.9.png
+++ b/core/res/res/drawable-mdpi/list_selected_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_green_up.png b/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_green_up.png
deleted file mode 100644
index cc46f19..0000000
--- a/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_green_up.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_red_up.png b/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_red_up.png
deleted file mode 100644
index cc46f19..0000000
--- a/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_red_up.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/list_divider_horizontal_holo_dark.9.png b/core/res/res/drawable-xhdpi/list_divider_horizontal_holo_dark.9.png
index a8ad54d..0c901de 100644
--- a/core/res/res/drawable-xhdpi/list_divider_horizontal_holo_dark.9.png
+++ b/core/res/res/drawable-xhdpi/list_divider_horizontal_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/list_longpressed_holo.9.png b/core/res/res/drawable-xhdpi/list_longpressed_holo.9.png
index e303022..eda10e6 100644
--- a/core/res/res/drawable-xhdpi/list_longpressed_holo.9.png
+++ b/core/res/res/drawable-xhdpi/list_longpressed_holo.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/list_selected_holo_dark.9.png b/core/res/res/drawable-xhdpi/list_selected_holo_dark.9.png
index 4375032..ee5eb6f 100644
--- a/core/res/res/drawable-xhdpi/list_selected_holo_dark.9.png
+++ b/core/res/res/drawable-xhdpi/list_selected_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/list_selected_holo_light.9.png b/core/res/res/drawable-xhdpi/list_selected_holo_light.9.png
index 4375032..ee5eb6f 100644
--- a/core/res/res/drawable-xhdpi/list_selected_holo_light.9.png
+++ b/core/res/res/drawable-xhdpi/list_selected_holo_light.9.png
Binary files differ
diff --git a/core/res/res/values-bg-rBG/donottranslate-cldr.xml b/core/res/res/values-bg-rBG/donottranslate-cldr.xml
deleted file mode 100644
index 4c38ad2..0000000
--- a/core/res/res/values-bg-rBG/donottranslate-cldr.xml
+++ /dev/null
@@ -1,149 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="month_long_standalone_january">януари</string>
-    <string name="month_long_standalone_february">февруари</string>
-    <string name="month_long_standalone_march">март</string>
-    <string name="month_long_standalone_april">април</string>
-    <string name="month_long_standalone_may">май</string>
-    <string name="month_long_standalone_june">юни</string>
-    <string name="month_long_standalone_july">юли</string>
-    <string name="month_long_standalone_august">август</string>
-    <string name="month_long_standalone_september">септември</string>
-    <string name="month_long_standalone_october">октомври</string>
-    <string name="month_long_standalone_november">ноември</string>
-    <string name="month_long_standalone_december">декември</string>
-
-    <string name="month_long_january">януари</string>
-    <string name="month_long_february">февруари</string>
-    <string name="month_long_march">март</string>
-    <string name="month_long_april">април</string>
-    <string name="month_long_may">май</string>
-    <string name="month_long_june">юни</string>
-    <string name="month_long_july">юли</string>
-    <string name="month_long_august">август</string>
-    <string name="month_long_september">септември</string>
-    <string name="month_long_october">октомври</string>
-    <string name="month_long_november">ноември</string>
-    <string name="month_long_december">декември</string>
-
-    <string name="month_medium_january">ян.</string>
-    <string name="month_medium_february">февр.</string>
-    <string name="month_medium_march">март</string>
-    <string name="month_medium_april">апр.</string>
-    <string name="month_medium_may">май</string>
-    <string name="month_medium_june">юни</string>
-    <string name="month_medium_july">юли</string>
-    <string name="month_medium_august">авг.</string>
-    <string name="month_medium_september">септ.</string>
-    <string name="month_medium_october">окт.</string>
-    <string name="month_medium_november">ноем.</string>
-    <string name="month_medium_december">дек.</string>
-
-    <string name="month_shortest_january">я</string>
-    <string name="month_shortest_february">ф</string>
-    <string name="month_shortest_march">м</string>
-    <string name="month_shortest_april">а</string>
-    <string name="month_shortest_may">м</string>
-    <string name="month_shortest_june">ю</string>
-    <string name="month_shortest_july">ю</string>
-    <string name="month_shortest_august">а</string>
-    <string name="month_shortest_september">с</string>
-    <string name="month_shortest_october">о</string>
-    <string name="month_shortest_november">н</string>
-    <string name="month_shortest_december">д</string>
-
-    <string name="day_of_week_long_sunday">неделя</string>
-    <string name="day_of_week_long_monday">понеделник</string>
-    <string name="day_of_week_long_tuesday">вторник</string>
-    <string name="day_of_week_long_wednesday">сряда</string>
-    <string name="day_of_week_long_thursday">четвъртък</string>
-    <string name="day_of_week_long_friday">петък</string>
-    <string name="day_of_week_long_saturday">събота</string>
-
-    <string name="day_of_week_medium_sunday">нд</string>
-    <string name="day_of_week_medium_monday">пн</string>
-    <string name="day_of_week_medium_tuesday">вт</string>
-    <string name="day_of_week_medium_wednesday">ср</string>
-    <string name="day_of_week_medium_thursday">чт</string>
-    <string name="day_of_week_medium_friday">пт</string>
-    <string name="day_of_week_medium_saturday">сб</string>
-
-    <string name="day_of_week_short_sunday">нд</string>
-    <string name="day_of_week_short_monday">пн</string>
-    <string name="day_of_week_short_tuesday">вт</string>
-    <string name="day_of_week_short_wednesday">ср</string>
-    <string name="day_of_week_short_thursday">чт</string>
-    <string name="day_of_week_short_friday">пт</string>
-    <string name="day_of_week_short_saturday">сб</string>
-
-    <string name="day_of_week_shortest_sunday">н</string>
-    <string name="day_of_week_shortest_monday">п</string>
-    <string name="day_of_week_shortest_tuesday">в</string>
-    <string name="day_of_week_shortest_wednesday">с</string>
-    <string name="day_of_week_shortest_thursday">ч</string>
-    <string name="day_of_week_shortest_friday">п</string>
-    <string name="day_of_week_shortest_saturday">с</string>
-
-    <string name="am">пр. об.</string>
-    <string name="pm">сл. об.</string>
-    <string name="yesterday">Вчера</string>
-    <string name="today">Днес</string>
-    <string name="tomorrow">Утре</string>
-
-    <string name="hour_minute_24">%-k:%M</string>
-    <string name="hour_minute_ampm">%-l:%M %p</string>
-    <string name="hour_minute_cap_ampm">%-l:%M %p</string>
-    <string name="twelve_hour_time_format">h:mm a</string>
-    <string name="twenty_four_hour_time_format">H:mm</string>
-    <string name="numeric_date">%d.%m.%Y</string>
-    <string name="numeric_date_format">dd.MM.yyyy</string>
-    <string name="numeric_date_template">"%s.%s.%s"</string>
-    <string name="month_day_year">%d %B %Y</string>
-    <string name="time_of_day">%H:%M:%S</string>
-    <string name="date_and_time">%H:%M:%S %d.%m.%Y</string>
-    <string name="date_time">%2$s %1$s</string>
-    <string name="time_date">%1$s %3$s</string>
-    <string name="abbrev_month_day_year">%d.%m.%Y</string>
-    <string name="month_day">%-e %B</string>
-    <string name="month">%-B</string>
-    <string name="month_year">%B %Y</string>
-    <string name="abbrev_month_day">%-e %b</string>
-    <string name="abbrev_month">%b</string>
-    <string name="abbrev_month_year">%b %Y</string>
-    <string name="time1_time2">%1$s-%2$s</string>
-    <string name="date1_date2">%2$s - %5$s</string>
-    <string name="numeric_md1_md2">%3$s.%2$s - %8$s.%7$s</string>
-    <string name="numeric_wday1_md1_wday2_md2">%3$s.%2$s, %1$s - %8$s.%7$s, %6$s</string>
-    <string name="numeric_mdy1_mdy2">%3$s.%2$s.%4$s - %8$s.%7$s.%9$s</string>
-    <string name="numeric_wday1_mdy1_wday2_mdy2">%3$s.%2$s.%4$s, %1$s - %8$s.%7$s.%9$s, %6$s</string>
-    <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">%5$s %3$s.%2$s.%4$s, %1$s - %10$s %8$s.%7$s.%9$s, %6$s</string>
-    <string name="numeric_md1_time1_md2_time2">%5$s %3$s.%2$s - %10$s %8$s.%7$s</string>
-    <string name="numeric_wday1_md1_time1_wday2_md2_time2">%5$s %3$s.%2$s, %1$s - %10$s %8$s.%7$s, %6$s</string>
-    <string name="numeric_mdy1_time1_mdy2_time2">%5$s %3$s.%2$s.%4$s - %10$s %8$s.%7$s.%9$s</string>
-    <string name="wday1_date1_time1_wday2_date2_time2">%3$s %2$s, %1$s - %6$s %5$s, %4$s</string>
-    <string name="wday1_date1_wday2_date2">%2$s, %1$s - %5$s, %4$s</string>
-    <string name="date1_time1_date2_time2">%3$s %2$s - %6$s %5$s</string>
-    <string name="time_wday_date">%1$s %3$s, %2$s</string>
-    <string name="wday_date">%3$s, %2$s</string>
-    <string name="time_wday">%1$s %2$s</string>
-    <string name="same_year_md1_md2">%3$s %2$s - %8$s %7$s</string>
-    <string name="same_year_wday1_md1_wday2_md2">%3$s %2$s, %1$s - %8$s %7$s, %6$s</string>
-    <string name="same_year_md1_time1_md2_time2">%5$s %3$s %2$s - %10$s %8$s %7$s</string>
-    <string name="same_month_md1_time1_md2_time2">%5$s %3$s %2$s - %10$s %8$s %7$s</string>
-    <string name="same_year_wday1_md1_time1_wday2_md2_time2">%5$s %3$s %2$s, %1$s - %10$s %8$s %7$s, %6$s</string>
-    <string name="same_month_wday1_md1_time1_wday2_md2_time2">%5$s %3$s %2$s, %1$s - %10$s %8$s %7$s, %6$s</string>
-    <string name="same_year_mdy1_time1_mdy2_time2">%5$s %3$s %2$s %4$s - %10$s %8$s %7$s %9$s</string>
-    <string name="same_month_mdy1_time1_mdy2_time2">%5$s %3$s %2$s %4$s - %10$s %8$s %7$s %9$s</string>
-    <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">%5$s %3$s %2$s %4$s, %1$s - %10$s %8$s %7$s %9$s, %6$s</string>
-    <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">%5$s %3$s %2$s %4$s, %1$s - %10$s %8$s %7$s %9$s, %6$s</string>
-    <string name="same_month_wday1_mdy1_wday2_mdy2">%3$s %2$s %4$s, %1$s - %8$s %7$s %9$s, %6$s</string>
-    <string name="same_month_md1_md2">%3$s-%8$s %2$s</string>
-    <string name="same_month_wday1_md1_wday2_md2">%3$s %2$s, %1$s - %8$s %7$s, %6$s</string>
-    <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s</string>
-    <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string>
-    <string name="same_year_wday1_mdy1_wday2_mdy2">%3$s %2$s %9$s, %1$s - %8$s %7$s y, %6$s</string>
-    <string name="short_format_month">%b</string>
-    <string name="full_wday_month_day_no_year">EEEE MMMM d</string>
-    <string name="abbrev_wday_month_day_year">d MMM yyyy, E</string>
-</resources>
diff --git a/core/res/res/values-bg/donottranslate-cldr.xml b/core/res/res/values-bg/donottranslate-cldr.xml
index 62f550a..dc8b3ad 100644
--- a/core/res/res/values-bg/donottranslate-cldr.xml
+++ b/core/res/res/values-bg/donottranslate-cldr.xml
@@ -128,22 +128,22 @@
     <string name="wday_date">%3$s, %2$s</string>
     <string name="time_wday">%1$s %2$s</string>
     <string name="same_year_md1_md2">%3$s %2$s - %8$s %7$s</string>
-    <string name="same_year_wday1_md1_wday2_md2">%1$s %2$s %3$s - %6$s %7$s %8$s</string>
+    <string name="same_year_wday1_md1_wday2_md2">%3$s %2$s, %1$s - %8$s %7$s, %6$s</string>
     <string name="same_year_md1_time1_md2_time2">%5$s %3$s %2$s - %10$s %8$s %7$s</string>
     <string name="same_month_md1_time1_md2_time2">%5$s %3$s %2$s - %10$s %8$s %7$s</string>
-    <string name="same_year_wday1_md1_time1_wday2_md2_time2">%5$s %1$s %2$s %3$s - %10$s %6$s %7$s %8$s</string>
-    <string name="same_month_wday1_md1_time1_wday2_md2_time2">%5$s %1$s %2$s %3$s - %10$s %6$s %7$s %8$s</string>
+    <string name="same_year_wday1_md1_time1_wday2_md2_time2">%5$s %3$s %2$s, %1$s - %10$s %8$s %7$s, %6$s</string>
+    <string name="same_month_wday1_md1_time1_wday2_md2_time2">%5$s %3$s %2$s, %1$s - %10$s %8$s %7$s, %6$s</string>
     <string name="same_year_mdy1_time1_mdy2_time2">%5$s %3$s %2$s %4$s - %10$s %8$s %7$s %9$s</string>
     <string name="same_month_mdy1_time1_mdy2_time2">%5$s %3$s %2$s %4$s - %10$s %8$s %7$s %9$s</string>
     <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">%5$s %3$s %2$s %4$s, %1$s - %10$s %8$s %7$s %9$s, %6$s</string>
     <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">%5$s %3$s %2$s %4$s, %1$s - %10$s %8$s %7$s %9$s, %6$s</string>
     <string name="same_month_wday1_mdy1_wday2_mdy2">%3$s %2$s %4$s, %1$s - %8$s %7$s %9$s, %6$s</string>
     <string name="same_month_md1_md2">%3$s-%8$s %2$s</string>
-    <string name="same_month_wday1_md1_wday2_md2">%1$s %2$s %3$s - %6$s %7$s %8$s</string>
+    <string name="same_month_wday1_md1_wday2_md2">%3$s %2$s, %1$s - %8$s %7$s, %6$s</string>
     <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s</string>
     <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string>
     <string name="same_year_wday1_mdy1_wday2_mdy2">%3$s %2$s %9$s, %1$s - %8$s %7$s y, %6$s</string>
     <string name="short_format_month">%b</string>
-    <string name="full_wday_month_day_no_year">E, d MMMM</string>
-    <string name="abbrev_wday_month_day_year">d MMM y, E</string>
+    <string name="full_wday_month_day_no_year">d MMMM, EEEE</string>
+    <string name="abbrev_wday_month_day_year">d MMM yyyy, E</string>
 </resources>
diff --git a/data/etc/android.hardware.wifi.direct.xml b/data/etc/android.hardware.wifi.direct.xml
new file mode 100644
index 0000000..78cb474
--- /dev/null
+++ b/data/etc/android.hardware.wifi.direct.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<!-- This is the standard feature indicating that the device includes WiFi Direct. -->
+<permissions>
+    <feature name="android.hardware.wifi.direct" />
+</permissions>
diff --git a/data/fonts/DroidSansFallback.ttf b/data/fonts/DroidSansFallback.ttf
index ba9d76f..ff97670 100644
--- a/data/fonts/DroidSansFallback.ttf
+++ b/data/fonts/DroidSansFallback.ttf
Binary files differ
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 72d233a3..0a3deb1 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -290,6 +290,8 @@
 
     /**
      * Implement this interface if you want to create an drawable that is RTL aware
+     *
+     * @hide
      */
     public static interface Callback2 extends Callback {
         /**
@@ -379,6 +381,8 @@
     /**
      * Use the current {@link android.graphics.drawable.Drawable.Callback2} implementation to get
      * the resolved layout direction of this Drawable.
+     *
+     * @hide
      */
     public int getResolvedLayoutDirectionSelf() {
         final Callback callback = getCallback();
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index 7e8c7fd..349b9e3 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -91,6 +91,13 @@
 #endif
 }
 
+// TODO: This implementation is flawed and can generate T-junctions
+//       in the mesh, which will in turn produce cracks when the
+//       mesh is rotated/skewed. The easiest way to fix this would
+//       be, for each row, to add new vertices shared with the previous
+//       row when the two rows share an edge.
+//       In practice, T-junctions do not appear often so this has yet
+//       to be fixed.
 void LayerRenderer::generateMesh() {
 #if RENDER_LAYERS_AS_REGIONS
     if (mLayer->region.isRect() || mLayer->region.isEmpty()) {
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index a20a88e..24784af 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -724,6 +724,8 @@
         return;
     }
 
+    // TODO: See LayerRenderer.cpp::generateMesh() for important
+    //       information about this implementation
     if (!layer->region.isEmpty()) {
         size_t count;
         const android::Rect* rects = layer->region.getArray(&count);
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index d233f92..9d9b6ea 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -1347,11 +1347,10 @@
                     // currently controlled by the same client process.
                     if ((AudioService.this.mMode == AudioSystem.MODE_NORMAL ||
                             mSetModeDeathHandlers.get(0).getPid() == mCreatorPid) &&
-                            mBluetoothHeadsetDevice != null &&
                             (mScoAudioState == SCO_STATE_INACTIVE ||
                              mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) {
                         if (mScoAudioState == SCO_STATE_INACTIVE) {
-                            if (mBluetoothHeadset != null) {
+                            if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) {
                                 if (mBluetoothHeadset.startScoUsingVirtualVoiceCall(
                                         mBluetoothHeadsetDevice)) {
                                     mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
@@ -1370,11 +1369,10 @@
                         broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
                     }
                 } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED &&
-                              mBluetoothHeadsetDevice != null &&
                               (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL ||
                                mScoAudioState == SCO_STATE_ACTIVATE_REQ)) {
                     if (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL) {
-                        if (mBluetoothHeadset != null) {
+                        if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) {
                             if (!mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
                                     mBluetoothHeadsetDevice)) {
                                 mScoAudioState = SCO_STATE_INACTIVE;
diff --git a/media/libmedia/IMediaPlayer.cpp b/media/libmedia/IMediaPlayer.cpp
index bd89ad8..50a41ca 100644
--- a/media/libmedia/IMediaPlayer.cpp
+++ b/media/libmedia/IMediaPlayer.cpp
@@ -108,6 +108,7 @@
         Parcel data, reply;
         data.writeInterfaceToken(IMediaPlayer::getInterfaceDescriptor());
         data.writeStrongBinder(source->asBinder());
+        remote()->transact(SET_DATA_SOURCE_STREAM, data, &reply);
         return reply.readInt32();
     }
 
diff --git a/media/libstagefright/MP3Extractor.cpp b/media/libstagefright/MP3Extractor.cpp
index 09f91f5..92e84c2 100644
--- a/media/libstagefright/MP3Extractor.cpp
+++ b/media/libstagefright/MP3Extractor.cpp
@@ -52,10 +52,7 @@
         *post_id3_pos = 0;
     }
 
-    bool resync_from_head = false;
     if (*inout_pos == 0) {
-        resync_from_head = true;
-
         // Skip an optional ID3 header if syncing at the very beginning
         // of the datasource.
 
@@ -140,20 +137,22 @@
 
         uint32_t header = U32_AT(tmp);
 
+        if (match_header != 0 && (header & kMask) != (match_header & kMask)) {
+            ++pos;
+            ++tmp;
+            --remainingBytes;
+            continue;
+        }
+
         size_t frame_size;
-        if ((match_header != 0 && (header & kMask) != (match_header & kMask))
-                || !GetMPEGAudioFrameSize(header, &frame_size)) {
-            if (resync_from_head) {
-                // This isn't a valid mp3 file because it failed to detect
-                // a header while a valid mp3 file should have a valid
-                // header here.
-                break;
-            } else {
-                ++pos;
-                ++tmp;
-                --remainingBytes;
-                continue;
-            }
+        int sample_rate, num_channels, bitrate;
+        if (!GetMPEGAudioFrameSize(
+                    header, &frame_size,
+                    &sample_rate, &num_channels, &bitrate)) {
+            ++pos;
+            ++tmp;
+            --remainingBytes;
+            continue;
         }
 
         LOGV("found possible 1st frame at %lld (header = 0x%08x)", pos, header);
diff --git a/media/libstagefright/StagefrightMetadataRetriever.cpp b/media/libstagefright/StagefrightMetadataRetriever.cpp
index 778c0b5..c74cb5a 100644
--- a/media/libstagefright/StagefrightMetadataRetriever.cpp
+++ b/media/libstagefright/StagefrightMetadataRetriever.cpp
@@ -283,8 +283,15 @@
         return NULL;
     }
 
+    sp<MetaData> fileMeta = mExtractor->getMetaData();
+
+    if (fileMeta == NULL) {
+        LOGV("extractor doesn't publish metadata, failed to initialize?");
+        return NULL;
+    }
+
     int32_t drm = 0;
-    if (mExtractor->getMetaData()->findInt32(kKeyIsDRM, &drm) && drm != 0) {
+    if (fileMeta->findInt32(kKeyIsDRM, &drm) && drm != 0) {
         LOGE("frame grab not allowed.");
         return NULL;
     }
@@ -320,7 +327,7 @@
     const void *data;
     uint32_t type;
     size_t dataSize;
-    if (mExtractor->getMetaData()->findData(kKeyAlbumArt, &type, &data, &dataSize)
+    if (fileMeta->findData(kKeyAlbumArt, &type, &data, &dataSize)
             && mAlbumArt == NULL) {
         mAlbumArt = new MediaAlbumArt;
         mAlbumArt->mSize = dataSize;
@@ -387,6 +394,11 @@
 void StagefrightMetadataRetriever::parseMetaData() {
     sp<MetaData> meta = mExtractor->getMetaData();
 
+    if (meta == NULL) {
+        LOGV("extractor doesn't publish metadata, failed to initialize?");
+        return;
+    }
+
     struct Map {
         int from;
         int to;
diff --git a/media/libstagefright/matroska/MatroskaExtractor.cpp b/media/libstagefright/matroska/MatroskaExtractor.cpp
index 3ef7b71..ffa3356 100644
--- a/media/libstagefright/matroska/MatroskaExtractor.cpp
+++ b/media/libstagefright/matroska/MatroskaExtractor.cpp
@@ -93,7 +93,7 @@
 
     void advance();
     void reset();
-    void seek(int64_t seekTimeUs);
+    void seek(int64_t seekTimeUs, bool seekToKeyFrame);
 
     const mkvparser::Block *block() const;
     int64_t blockTimeUs() const;
@@ -137,6 +137,7 @@
     sp<MatroskaExtractor> mExtractor;
     size_t mTrackIndex;
     Type mType;
+    bool mIsAudio;
     BlockIterator mBlockIter;
     size_t mNALSizeLen;  // for type AVC
 
@@ -156,6 +157,7 @@
     : mExtractor(extractor),
       mTrackIndex(index),
       mType(OTHER),
+      mIsAudio(false),
       mBlockIter(mExtractor.get(),
                  mExtractor->mTracks.itemAt(index).mTrackNum),
       mNALSizeLen(0) {
@@ -164,6 +166,8 @@
     const char *mime;
     CHECK(meta->findCString(kKeyMIMEType, &mime));
 
+    mIsAudio = !strncasecmp("audio/", mime, 6);
+
     if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) {
         mType = AVC;
 
@@ -299,7 +303,7 @@
     } while (!eos() && block()->GetTrackNumber() != mTrackNum);
 }
 
-void BlockIterator::seek(int64_t seekTimeUs) {
+void BlockIterator::seek(int64_t seekTimeUs, bool seekToKeyFrame) {
     Mutex::Autolock autoLock(mExtractor->mLock);
 
     mCluster = mExtractor->mSegment->FindCluster(seekTimeUs * 1000ll);
@@ -311,8 +315,10 @@
     }
     while (!eos() && block()->GetTrackNumber() != mTrackNum);
 
-    while (!eos() && !mBlockEntry->GetBlock()->IsKey()) {
-        advance_l();
+    if (seekToKeyFrame) {
+        while (!eos() && !mBlockEntry->GetBlock()->IsKey()) {
+            advance_l();
+        }
     }
 }
 
@@ -396,7 +402,11 @@
     if (options && options->getSeekTo(&seekTimeUs, &mode)
             && !mExtractor->isLiveStreaming()) {
         clearPendingFrames();
-        mBlockIter.seek(seekTimeUs);
+
+        // Apparently keyframe indication in audio tracks is unreliable,
+        // fortunately in all our currently supported audio encodings every
+        // frame is effectively a keyframe.
+        mBlockIter.seek(seekTimeUs, !mIsAudio);
     }
 
 again:
@@ -537,6 +547,13 @@
         return;
     }
 
+#if 0
+    const mkvparser::SegmentInfo *info = mSegment->GetInfo();
+    LOGI("muxing app: %s, writing app: %s",
+         info->GetMuxingAppAsUTF8(),
+         info->GetWritingAppAsUTF8());
+#endif
+
     addTracks();
 }
 
diff --git a/media/libstagefright/tests/SurfaceMediaSource_test.cpp b/media/libstagefright/tests/SurfaceMediaSource_test.cpp
index d663602..d7bb703 100644
--- a/media/libstagefright/tests/SurfaceMediaSource_test.cpp
+++ b/media/libstagefright/tests/SurfaceMediaSource_test.cpp
@@ -156,8 +156,6 @@
             eglDestroySurface(mEglDisplay, mEglSurface);
         }
         if (mEglDisplay != EGL_NO_DISPLAY) {
-            eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
-                    EGL_NO_CONTEXT);
             eglTerminate(mEglDisplay);
         }
         ASSERT_EQ(EGL_SUCCESS, eglGetError());
@@ -461,6 +459,7 @@
 
     // The following call dequeues and queues the buffer
     eglSwapBuffers(mEglDisplay, mEglSurface);
+    ASSERT_EQ(EGL_SUCCESS, eglGetError());
     glDisable(GL_SCISSOR_TEST);
 }
 
@@ -796,7 +795,12 @@
         LOGV("framesCount = %d", nFramesCount);
     }
 
-    ASSERT_EQ(NO_ERROR, native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_EGL));
+    EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+            EGL_NO_CONTEXT));
+    ASSERT_EQ(EGL_SUCCESS, eglGetError());
+    eglDestroySurface(mEglDisplay, mEglSurface);
+    mEglSurface = EGL_NO_SURFACE;
+
     writer.stop();
 }
 // Test to examine whether we can render GL buffers in to the surface
@@ -875,7 +879,12 @@
         LOGV("framesCount = %d", nFramesCount);
     }
 
-    ASSERT_EQ(NO_ERROR, native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_EGL));
+    EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+            EGL_NO_CONTEXT));
+    ASSERT_EQ(EGL_SUCCESS, eglGetError());
+    eglDestroySurface(mEglDisplay, mEglSurface);
+    mEglSurface = EGL_NO_SURFACE;
+
     LOGV("Stopping MediaRecorder...");
     CHECK_EQ(OK, mr->stop());
     mr.clear();
@@ -913,7 +922,12 @@
         LOGV("framesCount = %d", nFramesCount);
     }
 
-    ASSERT_EQ(NO_ERROR, native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_EGL));
+    EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+            EGL_NO_CONTEXT));
+    ASSERT_EQ(EGL_SUCCESS, eglGetError());
+    eglDestroySurface(mEglDisplay, mEglSurface);
+    mEglSurface = EGL_NO_SURFACE;
+
     LOGV("Stopping MediaRecorder...");
     CHECK_EQ(OK, mr->stop());
     mr.clear();
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
index 0810643..0f79515 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
@@ -26,6 +26,7 @@
 import android.hardware.Camera.PreviewCallback;
 import android.media.MediaPlayer;
 import android.media.MediaRecorder;
+import android.media.EncoderCapabilities.VideoEncoderCap;
 import android.os.ConditionVariable;
 import android.os.Looper;
 import android.os.SystemClock;
@@ -35,6 +36,7 @@
 import android.util.Log;
 import android.view.SurfaceHolder;
 
+import java.util.List;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -48,11 +50,12 @@
 import android.media.MediaMetadataRetriever;
 import com.android.mediaframeworktest.MediaProfileReader;
 
-import android.hardware.Camera.PreviewCallback;
-
 /**
  * Junit / Instrumentation - performance measurement for media player and 
  * recorder
+ *
+ * FIXME:
+ * Add tests on H264 video encoder
  */
 public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<MediaFrameworkTest> {
 
@@ -81,6 +84,8 @@
     private static int DECODER_LIMIT = 150;
     private static int CAMERA_LIMIT = 80;
 
+    private static List<VideoEncoderCap> videoEncoders = MediaProfileReader.getVideoEncoders();
+
     Camera mCamera;
 
     public MediaPlayerPerformance() {
@@ -360,6 +365,16 @@
         assertTrue("H264 playback memory test", memoryResult);
     }
 
+    private int getMaxFrameRateForVideoEncoder(int codec) {
+        int frameRate = -1;
+        for (VideoEncoderCap cap: videoEncoders) {
+            if (cap.mCodec == MediaRecorder.VideoEncoder.H263) {
+                frameRate = cap.mMaxFrameRate;
+            }
+        }
+        return frameRate;
+    }
+
     // Test case 4: Capture the memory usage after every 20 video only recorded
     @LargeTest
     public void testH263RecordVideoOnlyMemoryUsage() throws Exception {
@@ -369,8 +384,10 @@
         File videoH263RecordOnlyMemoryOut = new File(MEDIA_MEMORY_OUTPUT);
         Writer output = new BufferedWriter(new FileWriter(videoH263RecordOnlyMemoryOut, true));
         output.write("H263 video record only\n");
+        int frameRate = getMaxFrameRateForVideoEncoder(MediaRecorder.VideoEncoder.H263);
+        assertTrue("H263 video recording frame rate", frameRate != -1);
         for (int i = 0; i < NUM_STRESS_LOOP; i++) {
-            assertTrue(stressVideoRecord(20, 352, 288, MediaRecorder.VideoEncoder.H263,
+            assertTrue(stressVideoRecord(frameRate, 352, 288, MediaRecorder.VideoEncoder.H263,
                     MediaRecorder.OutputFormat.MPEG_4, MediaNames.RECORDED_VIDEO_3GP, true));
             getMemoryWriteToLog(output, i);
         }
@@ -389,8 +406,10 @@
         File videoMp4RecordOnlyMemoryOut = new File(MEDIA_MEMORY_OUTPUT);
         Writer output = new BufferedWriter(new FileWriter(videoMp4RecordOnlyMemoryOut, true));
         output.write("MPEG4 video record only\n");
+        int frameRate = getMaxFrameRateForVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);
+        assertTrue("MPEG4 video recording frame rate", frameRate != -1);
         for (int i = 0; i < NUM_STRESS_LOOP; i++) {
-            assertTrue(stressVideoRecord(20, 352, 288, MediaRecorder.VideoEncoder.MPEG_4_SP,
+            assertTrue(stressVideoRecord(frameRate, 352, 288, MediaRecorder.VideoEncoder.MPEG_4_SP,
                     MediaRecorder.OutputFormat.MPEG_4, MediaNames.RECORDED_VIDEO_3GP, true));
             getMemoryWriteToLog(output, i);
         }
@@ -409,9 +428,11 @@
 
         File videoRecordAudioMemoryOut = new File(MEDIA_MEMORY_OUTPUT);
         Writer output = new BufferedWriter(new FileWriter(videoRecordAudioMemoryOut, true));
+        int frameRate = getMaxFrameRateForVideoEncoder(MediaRecorder.VideoEncoder.H263);
+        assertTrue("H263 video recording frame rate", frameRate != -1);
         output.write("Audio and h263 video record\n");
         for (int i = 0; i < NUM_STRESS_LOOP; i++) {
-            assertTrue(stressVideoRecord(20, 352, 288, MediaRecorder.VideoEncoder.H263,
+            assertTrue(stressVideoRecord(frameRate, 352, 288, MediaRecorder.VideoEncoder.H263,
                     MediaRecorder.OutputFormat.MPEG_4, MediaNames.RECORDED_VIDEO_3GP, false));
             getMemoryWriteToLog(output, i);
         }
diff --git a/packages/SystemUI/res/layout-sw600dp/status_bar_notification_area.xml b/packages/SystemUI/res/layout-sw600dp/status_bar_notification_area.xml
index d235859..5e254e8 100644
--- a/packages/SystemUI/res/layout-sw600dp/status_bar_notification_area.xml
+++ b/packages/SystemUI/res/layout-sw600dp/status_bar_notification_area.xml
@@ -89,9 +89,10 @@
             <TextView android:id="@+id/time_solid"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
+                android:textAppearance="@style/TextAppearance.StatusBar.Clock"
                 android:singleLine="true"
                 android:textSize="40sp"
-                android:textColor="#ff525e79" />
+                />
         </com.android.systemui.statusbar.tablet.HoloClock>
 
         <TextView
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index f3d0bee..a63893e 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -72,13 +72,12 @@
 
         <com.android.systemui.statusbar.policy.Clock
             android:id="@+id/clock"
-            android:textAppearance="@*android:style/TextAppearance.StatusBar.Icon"
+            android:textAppearance="@style/TextAppearance.StatusBar.Clock"
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
             android:singleLine="true"
             android:paddingRight="6dip"
             android:textSize="16sp"
-            android:textStyle="bold"
             android:gravity="center_vertical|left"
             />
     </LinearLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 6f47ed0..0b3fb98 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -34,9 +34,7 @@
         android:paddingRight="3dp"
         >
         <com.android.systemui.statusbar.policy.DateView android:id="@+id/date"
-            android:textAppearance="@android:style/TextAppearance.StatusBar.EventContent.Title"
-            android:textColor="@android:color/holo_blue_light"
-            android:textStyle="normal"
+            android:textAppearance="@style/TextAppearance.StatusBar.Clock"
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
             android:layout_alignParentLeft="true"
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index ad236b7..3d49cd1 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -40,6 +40,12 @@
         <item name="android:textColor">#FFFFFFFF</item>
     </style>
 
+    <style name="TextAppearance.StatusBar.Clock" parent="@*android:style/TextAppearance.StatusBar.Icon">
+        <item name="android:textSize">16sp</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textColor">@android:color/holo_blue_light</item>
+    </style>
+
     <style name="Animation" />
 
     <style name="Animation.ShirtPocketPanel">
diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
index 62abf20..0de2de95 100644
--- a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
+++ b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
@@ -180,7 +180,7 @@
 
     private Runnable mRecreateRunnable = new Runnable() {
         public void run() {
-            updateScreen(mMode, false);
+            updateScreen(mMode, true);
         }
     };
 
@@ -400,7 +400,7 @@
             // then finish and get out
             if (mEnableFallback || mAccountIndex >= mAccounts.length) {
                 if (mUnlockScreen == null) {
-                    Log.w(TAG, "no unlock screen when trying to enable fallback");
+                    if (DEBUG) Log.w(TAG, "no unlock screen when trying to enable fallback");
                 } else if (mUnlockScreen instanceof PatternUnlockScreen) {
                     ((PatternUnlockScreen)mUnlockScreen).setEnableFallback(mEnableFallback);
                 }
@@ -459,7 +459,7 @@
     public void reset() {
         mIsVerifyUnlockOnly = false;
         mForgotPattern = false;
-        updateScreen(getInitialMode(), false);
+        post(mRecreateRunnable);
     }
 
     @Override
@@ -485,9 +485,7 @@
 
     private void recreateLockScreen() {
         if (mLockScreen != null) {
-            if (mLockScreen.getVisibility() == View.VISIBLE) {
-                ((KeyguardScreen) mLockScreen).onPause();
-            }
+            ((KeyguardScreen) mLockScreen).onPause();
             ((KeyguardScreen) mLockScreen).cleanUp();
             removeView(mLockScreen);
         }
@@ -499,9 +497,7 @@
 
     private void recreateUnlockScreen(UnlockMode unlockMode) {
         if (mUnlockScreen != null) {
-            if (mUnlockScreen.getVisibility() == View.VISIBLE) {
-                ((KeyguardScreen) mUnlockScreen).onPause();
-            }
+            ((KeyguardScreen) mUnlockScreen).onPause();
             ((KeyguardScreen) mUnlockScreen).cleanUp();
             removeView(mUnlockScreen);
         }
@@ -611,7 +607,7 @@
     private void updateScreen(Mode mode, boolean force) {
 
         if (DEBUG_CONFIGURATION) Log.v(TAG, "**** UPDATE SCREEN: mode=" + mode
-                + " last mode=" + mMode, new RuntimeException());
+                + " last mode=" + mMode + ", force = " + force, new RuntimeException());
 
         mMode = mode;
 
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 997318a..be2ef82 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -163,6 +163,10 @@
     private static final int MSG_FULL_CONFIRMATION_TIMEOUT = 9;
     private static final int MSG_RUN_FULL_RESTORE = 10;
 
+    // backup task state machine tick
+    static final int MSG_BACKUP_RESTORE_STEP = 20;
+    static final int MSG_OP_COMPLETE = 21;
+
     // Timeout interval for deciding that a bind or clear-data has taken too long
     static final long TIMEOUT_INTERVAL = 10 * 1000;
 
@@ -344,7 +348,16 @@
     static final int OP_ACKNOWLEDGED = 1;
     static final int OP_TIMEOUT = -1;
 
-    final SparseIntArray mCurrentOperations = new SparseIntArray();
+    class Operation {
+        public int state;
+        public BackupRestoreTask callback;
+
+        Operation(int initialState, BackupRestoreTask callbackObj) {
+            state = initialState;
+            callback = callbackObj;
+        }
+    }
+    final SparseArray<Operation> mCurrentOperations = new SparseArray<Operation>();
     final Object mCurrentOpLock = new Object();
     final Random mTokenGenerator = new Random();
 
@@ -442,13 +455,16 @@
                     }
                 }
 
+                // At this point, we have started a new journal file, and the old
+                // file identity is being passed to the backup processing task.
+                // When it completes successfully, that old journal file will be
+                // deleted.  If we crash prior to that, the old journal is parsed
+                // at next boot and the journaled requests fulfilled.
                 if (queue.size() > 0) {
-                    // At this point, we have started a new journal file, and the old
-                    // file identity is being passed to the backup processing thread.
-                    // When it completes successfully, that old journal file will be
-                    // deleted.  If we crash prior to that, the old journal is parsed
-                    // at next boot and the journaled requests fulfilled.
-                    (new PerformBackupTask(transport, queue, oldJournal)).run();
+                    // Spin up a backup state sequence and set it running
+                    PerformBackupTask pbt = new PerformBackupTask(transport, queue, oldJournal);
+                    Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
+                    sendMessage(pbtMessage);
                 } else {
                     Slog.v(TAG, "Backup requested but nothing pending");
                     mWakelock.release();
@@ -456,6 +472,29 @@
                 break;
             }
 
+            case MSG_BACKUP_RESTORE_STEP:
+            {
+                try {
+                    BackupRestoreTask task = (BackupRestoreTask) msg.obj;
+                    if (MORE_DEBUG) Slog.v(TAG, "Got next step for " + task + ", executing");
+                    task.execute();
+                } catch (ClassCastException e) {
+                    Slog.e(TAG, "Invalid backup task in flight, obj=" + msg.obj);
+                }
+                break;
+            }
+
+            case MSG_OP_COMPLETE:
+            {
+                try {
+                    BackupRestoreTask task = (BackupRestoreTask) msg.obj;
+                    task.operationComplete();
+                } catch (ClassCastException e) {
+                    Slog.e(TAG, "Invalid completion in flight, obj=" + msg.obj);
+                }
+                break;
+            }
+
             case MSG_RUN_FULL_BACKUP:
             {
                 FullBackupParams params = (FullBackupParams)msg.obj;
@@ -469,9 +508,12 @@
             {
                 RestoreParams params = (RestoreParams)msg.obj;
                 Slog.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer);
-                (new PerformRestoreTask(params.transport, params.observer,
+                PerformRestoreTask task = new PerformRestoreTask(
+                        params.transport, params.observer,
                         params.token, params.pkgInfo, params.pmToken,
-                        params.needFullBackup, params.filterSet)).run();
+                        params.needFullBackup, params.filterSet);
+                Message restoreMsg = obtainMessage(MSG_BACKUP_RESTORE_STEP, task);
+                sendMessage(restoreMsg);
                 break;
             }
 
@@ -540,15 +582,7 @@
 
             case MSG_TIMEOUT:
             {
-                synchronized (mCurrentOpLock) {
-                    final int token = msg.arg1;
-                    int state = mCurrentOperations.get(token, OP_TIMEOUT);
-                    if (state == OP_PENDING) {
-                        if (DEBUG) Slog.v(TAG, "TIMEOUT: token=" + token);
-                        mCurrentOperations.put(token, OP_TIMEOUT);
-                    }
-                    mCurrentOpLock.notifyAll();
-                }
+                handleTimeout(msg.arg1, msg.obj);
                 break;
             }
 
@@ -558,7 +592,7 @@
                     if (mActiveRestoreSession != null) {
                         // Client app left the restore session dangling.  We know that it
                         // can't be in the middle of an actual restore operation because
-                        // those are executed serially on this same handler thread.  Clean
+                        // the timeout is suspended while a restore is in progress.  Clean
                         // up now.
                         Slog.w(TAG, "Restore session timed out; aborting");
                         post(mActiveRestoreSession.new EndRestoreRunnable(
@@ -1113,12 +1147,14 @@
         }
 
         // Enqueue a new backup of every participant
-        int N = mBackupParticipants.size();
-        for (int i=0; i<N; i++) {
-            int uid = mBackupParticipants.keyAt(i);
-            HashSet<ApplicationInfo> participants = mBackupParticipants.valueAt(i);
-            for (ApplicationInfo app: participants) {
-                dataChangedImpl(app.packageName);
+        synchronized (mBackupParticipants) {
+            int N = mBackupParticipants.size();
+            for (int i=0; i<N; i++) {
+                int uid = mBackupParticipants.keyAt(i);
+                HashSet<ApplicationInfo> participants = mBackupParticipants.valueAt(i);
+                for (ApplicationInfo app: participants) {
+                    dataChangedImpl(app.packageName);
+                }
             }
         }
     }
@@ -1588,50 +1624,120 @@
     }
 
     // -----
-    // Utility methods used by the asynchronous-with-timeout backup/restore operations
-    boolean waitUntilOperationComplete(int token) {
-        int finalState = OP_PENDING;
+    // Interface and methods used by the asynchronous-with-timeout backup/restore operations
+
+    interface BackupRestoreTask {
+        // Execute one tick of whatever state machine the task implements
+        void execute();
+
+        // An operation that wanted a callback has completed
+        void operationComplete();
+
+        // An operation that wanted a callback has timed out
+        void handleTimeout();
+    }
+
+    void prepareOperationTimeout(int token, long interval, BackupRestoreTask callback) {
+        if (MORE_DEBUG) Slog.v(TAG, "starting timeout: token=" + Integer.toHexString(token)
+                + " interval=" + interval);
         synchronized (mCurrentOpLock) {
-            try {
-                while ((finalState = mCurrentOperations.get(token, OP_TIMEOUT)) == OP_PENDING) {
-                    try {
-                        mCurrentOpLock.wait();
-                    } catch (InterruptedException e) {}
+            mCurrentOperations.put(token, new Operation(OP_PENDING, callback));
+
+            Message msg = mBackupHandler.obtainMessage(MSG_TIMEOUT, token, 0, callback);
+            mBackupHandler.sendMessageDelayed(msg, interval);
+        }
+    }
+
+    // synchronous waiter case
+    boolean waitUntilOperationComplete(int token) {
+        if (MORE_DEBUG) Slog.i(TAG, "Blocking until operation complete for "
+                + Integer.toHexString(token));
+        int finalState = OP_PENDING;
+        Operation op = null;
+        synchronized (mCurrentOpLock) {
+            while (true) {
+                op = mCurrentOperations.get(token);
+                if (op == null) {
+                    // mysterious disappearance: treat as success with no callback
+                    break;
+                } else {
+                    if (op.state == OP_PENDING) {
+                        try {
+                            mCurrentOpLock.wait();
+                        } catch (InterruptedException e) {}
+                        // When the wait is notified we loop around and recheck the current state
+                    } else {
+                        // No longer pending; we're done
+                        finalState = op.state;
+                        break;
+                    }
                 }
-            } catch (IndexOutOfBoundsException e) {
-                // the operation has been mysteriously cleared from our
-                // bookkeeping -- consider this a success and ignore it.
             }
         }
+
         mBackupHandler.removeMessages(MSG_TIMEOUT);
         if (MORE_DEBUG) Slog.v(TAG, "operation " + Integer.toHexString(token)
                 + " complete: finalState=" + finalState);
         return finalState == OP_ACKNOWLEDGED;
     }
 
-    void prepareOperationTimeout(int token, long interval) {
-        if (MORE_DEBUG) Slog.v(TAG, "starting timeout: token=" + Integer.toHexString(token)
-                + " interval=" + interval);
+    void handleTimeout(int token, Object obj) {
+        // Notify any synchronous waiters
+        Operation op = null;
         synchronized (mCurrentOpLock) {
-            mCurrentOperations.put(token, OP_PENDING);
-            Message msg = mBackupHandler.obtainMessage(MSG_TIMEOUT, token, 0);
-            mBackupHandler.sendMessageDelayed(msg, interval);
+            op = mCurrentOperations.get(token);
+            if (MORE_DEBUG) {
+                if (op == null) Slog.w(TAG, "Timeout of token " + Integer.toHexString(token)
+                        + " but no op found");
+            }
+            int state = (op != null) ? op.state : OP_TIMEOUT;
+            if (state == OP_PENDING) {
+                if (DEBUG) Slog.v(TAG, "TIMEOUT: token=" + Integer.toHexString(token));
+                op.state = OP_TIMEOUT;
+                mCurrentOperations.put(token, op);
+            }
+            mCurrentOpLock.notifyAll();
+        }
+
+        // If there's a TimeoutHandler for this event, call it
+        if (op != null && op.callback != null) {
+            op.callback.handleTimeout();
         }
     }
 
     // ----- Back up a set of applications via a worker thread -----
 
-    class PerformBackupTask implements Runnable {
-        private static final String TAG = "PerformBackupThread";
+    enum BackupState {
+        INITIAL,
+        RUNNING_QUEUE,
+        FINAL
+    }
+
+    class PerformBackupTask implements BackupRestoreTask {
+        private static final String TAG = "PerformBackupTask";
+
         IBackupTransport mTransport;
         ArrayList<BackupRequest> mQueue;
+        ArrayList<BackupRequest> mOriginalQueue;
         File mStateDir;
         File mJournal;
+        BackupState mCurrentState;
+
+        // carried information about the current in-flight operation
+        PackageInfo mCurrentPackage;
+        File mSavedStateName;
+        File mBackupDataName;
+        File mNewStateName;
+        ParcelFileDescriptor mSavedState;
+        ParcelFileDescriptor mBackupData;
+        ParcelFileDescriptor mNewState;
+        int mStatus;
+        boolean mFinished;
 
         public PerformBackupTask(IBackupTransport transport, ArrayList<BackupRequest> queue,
                 File journal) {
             mTransport = transport;
-            mQueue = queue;
+            mOriginalQueue = queue;
             mJournal = journal;
 
             try {
@@ -1639,26 +1745,62 @@
             } catch (RemoteException e) {
                 // can't happen; the transport is local
             }
+
+            mCurrentState = BackupState.INITIAL;
+            mFinished = false;
         }
 
-        public void run() {
-            int status = BackupConstants.TRANSPORT_OK;
-            long startRealtime = SystemClock.elapsedRealtime();
+        // Main entry point: perform one chunk of work, updating the state as appropriate
+        // and reposting the next chunk to the primary backup handler thread.
+        @Override
+        public void execute() {
+            switch (mCurrentState) {
+                case INITIAL:
+                    beginBackup();
+                    break;
+
+                case RUNNING_QUEUE:
+                    invokeNextAgent();
+                    break;
+
+                case FINAL:
+                    if (!mFinished) finalizeBackup();
+                    else {
+                        Slog.e(TAG, "Duplicate finish");
+                    }
+                    mFinished = true;
+                    break;
+            }
+        }
+
+        // We're starting a backup pass.  Initialize the transport and send
+        // the PM metadata blob if we haven't already.
+        void beginBackup() {
+            mStatus = BackupConstants.TRANSPORT_OK;
+
+            // Sanity check: if the queue is empty we have no work to do.
+            if (mOriginalQueue.isEmpty()) {
+                Slog.w(TAG, "Backup begun with an empty queue - nothing to do.");
+                return;
+            }
+
+            // We need to retain the original queue contents in case of transport
+            // failure, but we want a working copy that we can manipulate along
+            // the way.
+            mQueue = (ArrayList<BackupRequest>) mOriginalQueue.clone();
+
             if (DEBUG) Slog.v(TAG, "Beginning backup of " + mQueue.size() + " targets");
 
-            // Backups run at background priority
-            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-
+            File pmState = new File(mStateDir, PACKAGE_MANAGER_SENTINEL);
             try {
                 EventLog.writeEvent(EventLogTags.BACKUP_START, mTransport.transportDirName());
 
                 // If we haven't stored package manager metadata yet, we must init the transport.
-                File pmState = new File(mStateDir, PACKAGE_MANAGER_SENTINEL);
-                if (status == BackupConstants.TRANSPORT_OK && pmState.length() <= 0) {
+                if (mStatus == BackupConstants.TRANSPORT_OK && pmState.length() <= 0) {
                     Slog.i(TAG, "Initializing (wiping) backup state and transport storage");
                     resetBackupState(mStateDir);  // Just to make sure.
-                    status = mTransport.initializeDevice();
-                    if (status == BackupConstants.TRANSPORT_OK) {
+                    mStatus = mTransport.initializeDevice();
+                    if (mStatus == BackupConstants.TRANSPORT_OK) {
                         EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE);
                     } else {
                         EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, "(initialize)");
@@ -1671,207 +1813,219 @@
                 // directly and use a synthetic BackupRequest.  We always run this pass
                 // because it's cheap and this way we guarantee that we don't get out of
                 // step even if we're selecting among various transports at run time.
-                if (status == BackupConstants.TRANSPORT_OK) {
+                if (mStatus == BackupConstants.TRANSPORT_OK) {
                     PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(
                             mPackageManager, allAgentPackages());
-                    status = processOneBackup(PACKAGE_MANAGER_SENTINEL,
+                    mStatus = invokeAgentForBackup(PACKAGE_MANAGER_SENTINEL,
                             IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport);
                 }
 
-                if (status == BackupConstants.TRANSPORT_OK) {
-                    // Now run all the backups in our queue
-                    status = doQueuedBackups(mTransport);
-                }
-
-                if (status == BackupConstants.TRANSPORT_OK) {
-                    // Tell the transport to finish everything it has buffered
-                    status = mTransport.finishBackup();
-                    if (status == BackupConstants.TRANSPORT_OK) {
-                        int millis = (int) (SystemClock.elapsedRealtime() - startRealtime);
-                        EventLog.writeEvent(EventLogTags.BACKUP_SUCCESS, mQueue.size(), millis);
-                    } else {
-                        EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, "(finish)");
-                        Slog.e(TAG, "Transport error in finishBackup()");
-                    }
-                }
-
-                if (status == BackupConstants.TRANSPORT_NOT_INITIALIZED) {
-                    // The backend reports that our dataset has been wiped.  We need to
-                    // reset all of our bookkeeping and instead run a new backup pass for
-                    // everything.
+                if (mStatus == BackupConstants.TRANSPORT_NOT_INITIALIZED) {
+                    // The backend reports that our dataset has been wiped.  Note this in
+                    // the event log; the no-success code below will reset the backup
+                    // state as well.
                     EventLog.writeEvent(EventLogTags.BACKUP_RESET, mTransport.transportDirName());
-                    resetBackupState(mStateDir);
                 }
             } catch (Exception e) {
                 Slog.e(TAG, "Error in backup thread", e);
-                status = BackupConstants.TRANSPORT_ERROR;
+                mStatus = BackupConstants.TRANSPORT_ERROR;
             } finally {
-                // If everything actually went through and this is the first time we've
-                // done a backup, we can now record what the current backup dataset token
-                // is.
-                if ((mCurrentToken == 0) && (status == BackupConstants.TRANSPORT_OK)) {
-                    try {
-                        mCurrentToken = mTransport.getCurrentRestoreSet();
-                    } catch (RemoteException e) { /* cannot happen */ }
-                    writeRestoreTokens();
+                // If we've succeeded so far, invokeAgentForBackup() will have run the PM
+                // metadata and its completion/timeout callback will continue the state
+                // machine chain.  If it failed that won't happen; we handle that now.
+                if (mStatus != BackupConstants.TRANSPORT_OK) {
+                    // if things went wrong at this point, we need to
+                    // restage everything and try again later.
+                    resetBackupState(mStateDir);  // Just to make sure.
+                    executeNextState(BackupState.FINAL);
                 }
-
-                // If things went wrong, we need to re-stage the apps we had expected
-                // to be backing up in this pass.  This journals the package names in
-                // the current active pending-backup file, not in the we are holding
-                // here in mJournal.
-                if (status != BackupConstants.TRANSPORT_OK) {
-                    Slog.w(TAG, "Backup pass unsuccessful, restaging");
-                    for (BackupRequest req : mQueue) {
-                        dataChangedImpl(req.packageName);
-                    }
-
-                    // We also want to reset the backup schedule based on whatever
-                    // the transport suggests by way of retry/backoff time.
-                    try {
-                        startBackupAlarmsLocked(mTransport.requestBackupTime());
-                    } catch (RemoteException e) { /* cannot happen */ }
-                }
-
-                // Either backup was successful, in which case we of course do not need
-                // this pass's journal any more; or it failed, in which case we just
-                // re-enqueued all of these packages in the current active journal.
-                // Either way, we no longer need this pass's journal.
-                if (mJournal != null && !mJournal.delete()) {
-                    Slog.e(TAG, "Unable to remove backup journal file " + mJournal);
-                }
-
-                // Only once we're entirely finished do we release the wakelock
-                if (status == BackupConstants.TRANSPORT_NOT_INITIALIZED) {
-                    backupNow();
-                }
-
-                mWakelock.release();
             }
         }
 
-        private int doQueuedBackups(IBackupTransport transport) {
-            for (BackupRequest request : mQueue) {
-                Slog.d(TAG, "starting agent for backup of " + request);
+        // Transport has been initialized and the PM metadata submitted successfully
+        // if that was warranted.  Now we process the single next thing in the queue.
+        void invokeNextAgent() {
+            mStatus = BackupConstants.TRANSPORT_OK;
 
-                // Verify that the requested app exists; it might be something that
-                // requested a backup but was then uninstalled.  The request was
-                // journalled and rather than tamper with the journal it's safer
-                // to sanity-check here.  This also gives us the classname of the
-                // package's backup agent.
-                PackageInfo pkg;
-                try {
-                    pkg = mPackageManager.getPackageInfo(request.packageName, 0);
-                } catch (NameNotFoundException e) {
-                    Slog.d(TAG, "Package does not exist; skipping");
-                    continue;
-                }
+            // Sanity check that we have work to do.  If not, skip to the end where
+            // we reestablish the wakelock invariants etc.
+            if (mQueue.isEmpty()) {
+                Slog.e(TAG, "Running queue but it's empty!");
+                executeNextState(BackupState.FINAL);
+                return;
+            }
+
+            // pop the entry we're going to process on this step
+            BackupRequest request = mQueue.get(0);
+            mQueue.remove(0);
+
+            Slog.d(TAG, "starting agent for backup of " + request);
+
+            // Verify that the requested app exists; it might be something that
+            // requested a backup but was then uninstalled.  The request was
+            // journalled and rather than tamper with the journal it's safer
+            // to sanity-check here.  This also gives us the classname of the
+            // package's backup agent.
+            try {
+                mCurrentPackage = mPackageManager.getPackageInfo(request.packageName,
+                        PackageManager.GET_SIGNATURES);
 
                 IBackupAgent agent = null;
                 try {
-                    mWakelock.setWorkSource(new WorkSource(pkg.applicationInfo.uid));
-                    agent = bindToAgentSynchronous(pkg.applicationInfo,
+                    mWakelock.setWorkSource(new WorkSource(mCurrentPackage.applicationInfo.uid));
+                    agent = bindToAgentSynchronous(mCurrentPackage.applicationInfo,
                             IApplicationThread.BACKUP_MODE_INCREMENTAL);
                     if (agent != null) {
-                        int result = processOneBackup(request.packageName, agent, transport);
-                        if (result != BackupConstants.TRANSPORT_OK) return result;
+                        mStatus = invokeAgentForBackup(request.packageName, agent, mTransport);
+                        // at this point we'll either get a completion callback from the
+                        // agent, or a timeout message on the main handler.  either way, we're
+                        // done here as long as we're successful so far.
+                    } else {
+                        // Timeout waiting for the agent
+                        mStatus = BackupConstants.AGENT_ERROR;
                     }
                 } catch (SecurityException ex) {
                     // Try for the next one.
                     Slog.d(TAG, "error in bind/backup", ex);
-                } finally {
-                    try {  // unbind even on timeout, just in case
-                        mActivityManager.unbindBackupAgent(pkg.applicationInfo);
-                    } catch (RemoteException e) {}
+                    mStatus = BackupConstants.AGENT_ERROR;
+                }
+            } catch (NameNotFoundException e) {
+                Slog.d(TAG, "Package does not exist; skipping");
+            } finally {
+                mWakelock.setWorkSource(null);
+
+                // If there was an agent error, no timeout/completion handling will occur.
+                // That means we need to deal with the next state ourselves.
+                if (mStatus != BackupConstants.TRANSPORT_OK) {
+                    BackupState nextState = BackupState.RUNNING_QUEUE;
+
+                    // An agent-level failure means we reenqueue this one agent for
+                    // a later retry, but otherwise proceed normally.
+                    if (mStatus == BackupConstants.AGENT_ERROR) {
+                        if (MORE_DEBUG) Slog.i(TAG, "Agent failure for " + request.packageName
+                                + " - restaging");
+                        dataChangedImpl(request.packageName);
+                        mStatus = BackupConstants.TRANSPORT_OK;
+                        if (mQueue.isEmpty()) nextState = BackupState.FINAL;
+                    } else if (mStatus != BackupConstants.TRANSPORT_OK) {
+                        // Transport-level failure means we reenqueue everything
+                        revertAndEndBackup();
+                        nextState = BackupState.FINAL;
+                    }
+
+                    executeNextState(nextState);
                 }
             }
-
-            mWakelock.setWorkSource(null);
-
-            return BackupConstants.TRANSPORT_OK;
         }
 
-        private int processOneBackup(String packageName, IBackupAgent agent,
+        void finalizeBackup() {
+            // Either backup was successful, in which case we of course do not need
+            // this pass's journal any more; or it failed, in which case we just
+            // re-enqueued all of these packages in the current active journal.
+            // Either way, we no longer need this pass's journal.
+            if (mJournal != null && !mJournal.delete()) {
+                Slog.e(TAG, "Unable to remove backup journal file " + mJournal);
+            }
+
+            // If everything actually went through and this is the first time we've
+            // done a backup, we can now record what the current backup dataset token
+            // is.
+            if ((mCurrentToken == 0) && (mStatus == BackupConstants.TRANSPORT_OK)) {
+                try {
+                    mCurrentToken = mTransport.getCurrentRestoreSet();
+                } catch (RemoteException e) {} // can't happen
+                writeRestoreTokens();
+            }
+
+            // Set up the next backup pass
+            if (mStatus == BackupConstants.TRANSPORT_NOT_INITIALIZED) {
+                backupNow();
+            }
+
+            // Only once we're entirely finished do we release the wakelock
+            Slog.i(TAG, "Backup pass finished.");
+            mWakelock.release();
+        }
+
+        // Invoke an agent's doBackup() and start a timeout message spinning on the main
+        // handler in case it doesn't get back to us.
+        int invokeAgentForBackup(String packageName, IBackupAgent agent,
                 IBackupTransport transport) {
             if (DEBUG) Slog.d(TAG, "processOneBackup doBackup() on " + packageName);
 
-            File savedStateName = new File(mStateDir, packageName);
-            File backupDataName = new File(mDataDir, packageName + ".data");
-            File newStateName = new File(mStateDir, packageName + ".new");
+            mSavedStateName = new File(mStateDir, packageName);
+            mBackupDataName = new File(mDataDir, packageName + ".data");
+            mNewStateName = new File(mStateDir, packageName + ".new");
 
-            ParcelFileDescriptor savedState = null;
-            ParcelFileDescriptor backupData = null;
-            ParcelFileDescriptor newState = null;
+            mSavedState = null;
+            mBackupData = null;
+            mNewState = null;
 
-            PackageInfo packInfo;
             final int token = generateToken();
             try {
                 // Look up the package info & signatures.  This is first so that if it
                 // throws an exception, there's no file setup yet that would need to
                 // be unraveled.
                 if (packageName.equals(PACKAGE_MANAGER_SENTINEL)) {
-                    // The metadata 'package' is synthetic
-                    packInfo = new PackageInfo();
-                    packInfo.packageName = packageName;
-                } else {
-                    packInfo = mPackageManager.getPackageInfo(packageName,
-                        PackageManager.GET_SIGNATURES);
+                    // The metadata 'package' is synthetic; construct one and make
+                    // sure our global state is pointed at it
+                    mCurrentPackage = new PackageInfo();
+                    mCurrentPackage.packageName = packageName;
                 }
 
                 // In a full backup, we pass a null ParcelFileDescriptor as
                 // the saved-state "file". This is by definition an incremental,
                 // so we build a saved state file to pass.
-                savedState = ParcelFileDescriptor.open(savedStateName,
+                mSavedState = ParcelFileDescriptor.open(mSavedStateName,
                         ParcelFileDescriptor.MODE_READ_ONLY |
                         ParcelFileDescriptor.MODE_CREATE);  // Make an empty file if necessary
 
-                backupData = ParcelFileDescriptor.open(backupDataName,
+                mBackupData = ParcelFileDescriptor.open(mBackupDataName,
                         ParcelFileDescriptor.MODE_READ_WRITE |
                         ParcelFileDescriptor.MODE_CREATE |
                         ParcelFileDescriptor.MODE_TRUNCATE);
 
-                newState = ParcelFileDescriptor.open(newStateName,
+                mNewState = ParcelFileDescriptor.open(mNewStateName,
                         ParcelFileDescriptor.MODE_READ_WRITE |
                         ParcelFileDescriptor.MODE_CREATE |
                         ParcelFileDescriptor.MODE_TRUNCATE);
 
                 // Initiate the target's backup pass
-                prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL);
-                agent.doBackup(savedState, backupData, newState, token, mBackupManagerBinder);
-                boolean success = waitUntilOperationComplete(token);
-
-                if (!success) {
-                    // timeout -- bail out into the failed-transaction logic
-                    throw new RuntimeException("Backup timeout");
-                }
-
-                logBackupComplete(packageName);
-                if (DEBUG) Slog.v(TAG, "doBackup() success");
+                prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, this);
+                agent.doBackup(mSavedState, mBackupData, mNewState, token, mBackupManagerBinder);
             } catch (Exception e) {
-                Slog.e(TAG, "Error backing up " + packageName, e);
-                EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName, e.toString());
-                backupDataName.delete();
-                newStateName.delete();
-                return BackupConstants.TRANSPORT_ERROR;
-            } finally {
-                try { if (savedState != null) savedState.close(); } catch (IOException e) {}
-                try { if (backupData != null) backupData.close(); } catch (IOException e) {}
-                try { if (newState != null) newState.close(); } catch (IOException e) {}
-                savedState = backupData = newState = null;
-                synchronized (mCurrentOpLock) {
-                    mCurrentOperations.clear();
-                }
+                Slog.e(TAG, "Error invoking for backup on " + packageName);
+                EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName,
+                        e.toString());
+                agentErrorCleanup();
+                return BackupConstants.AGENT_ERROR;
             }
 
-            // Now propagate the newly-backed-up data to the transport
-            int result = BackupConstants.TRANSPORT_OK;
+            // At this point the agent is off and running.  The next thing to happen will
+            // either be a callback from the agent, at which point we'll process its data
+            // for transport, or a timeout.  Either way the next phase will happen in
+            // response to the TimeoutHandler interface callbacks.
+            return BackupConstants.TRANSPORT_OK;
+        }
+
+        @Override
+        public void operationComplete() {
+            // Okay, the agent successfully reported back to us.  Spin the data off to the
+            // transport and proceed with the next stage.
+            if (MORE_DEBUG) Slog.v(TAG, "operationComplete(): sending data to transport for "
+                    + mCurrentPackage.packageName);
+            mBackupHandler.removeMessages(MSG_TIMEOUT);
+            clearAgentState();
+
+            ParcelFileDescriptor backupData = null;
+            mStatus = BackupConstants.TRANSPORT_OK;
             try {
-                int size = (int) backupDataName.length();
+                int size = (int) mBackupDataName.length();
                 if (size > 0) {
-                    if (result == BackupConstants.TRANSPORT_OK) {
-                        backupData = ParcelFileDescriptor.open(backupDataName,
+                    if (mStatus == BackupConstants.TRANSPORT_OK) {
+                        backupData = ParcelFileDescriptor.open(mBackupDataName,
                                 ParcelFileDescriptor.MODE_READ_ONLY);
-                        result = transport.performBackup(packInfo, backupData);
+                        mStatus = mTransport.performBackup(mCurrentPackage, backupData);
                     }
 
                     // TODO - We call finishBackup() for each application backed up, because
@@ -1879,8 +2033,8 @@
                     // hold off on finishBackup() until the end, which implies holding off on
                     // renaming *all* the output state files (see below) until that happens.
 
-                    if (result == BackupConstants.TRANSPORT_OK) {
-                        result = transport.finishBackup();
+                    if (mStatus == BackupConstants.TRANSPORT_OK) {
+                        mStatus = mTransport.finishBackup();
                     }
                 } else {
                     if (DEBUG) Slog.i(TAG, "no backup data written; not calling transport");
@@ -1889,22 +2043,102 @@
                 // After successful transport, delete the now-stale data
                 // and juggle the files so that next time we supply the agent
                 // with the new state file it just created.
-                if (result == BackupConstants.TRANSPORT_OK) {
-                    backupDataName.delete();
-                    newStateName.renameTo(savedStateName);
-                    EventLog.writeEvent(EventLogTags.BACKUP_PACKAGE, packageName, size);
+                if (mStatus == BackupConstants.TRANSPORT_OK) {
+                    mBackupDataName.delete();
+                    mNewStateName.renameTo(mSavedStateName);
+                    EventLog.writeEvent(EventLogTags.BACKUP_PACKAGE,
+                            mCurrentPackage.packageName, size);
+                    logBackupComplete(mCurrentPackage.packageName);
                 } else {
-                    EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, packageName);
+                    EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE,
+                            mCurrentPackage.packageName);
                 }
             } catch (Exception e) {
-                Slog.e(TAG, "Transport error backing up " + packageName, e);
-                EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, packageName);
-                result = BackupConstants.TRANSPORT_ERROR;
+                Slog.e(TAG, "Transport error backing up " + mCurrentPackage.packageName, e);
+                EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE,
+                        mCurrentPackage.packageName);
+                mStatus = BackupConstants.TRANSPORT_ERROR;
             } finally {
                 try { if (backupData != null) backupData.close(); } catch (IOException e) {}
             }
 
-            return result;
+            // If we encountered an error here it's a transport-level failure.  That
+            // means we need to halt everything and reschedule everything for next time.
+            final BackupState nextState;
+            if (mStatus != BackupConstants.TRANSPORT_OK) {
+                revertAndEndBackup();
+                nextState = BackupState.FINAL;
+            } else {
+                // Success!  Proceed with the next app if any, otherwise we're done.
+                nextState = (mQueue.isEmpty()) ? BackupState.FINAL : BackupState.RUNNING_QUEUE;
+            }
+
+            executeNextState(nextState);
+        }
+
+        @Override
+        public void handleTimeout() {
+            // Whoops, the current agent timed out running doBackup().  Tidy up and restage
+            // it for the next time we run a backup pass.
+            // !!! TODO: keep track of failure counts per agent, and blacklist those which
+            // fail repeatedly (i.e. have proved themselves to be buggy).
+            Slog.e(TAG, "Timeout backing up " + mCurrentPackage.packageName);
+            EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, mCurrentPackage.packageName,
+                    "timeout");
+            agentErrorCleanup();
+            dataChangedImpl(mCurrentPackage.packageName);
+        }
+
+        void revertAndEndBackup() {
+            if (MORE_DEBUG) Slog.i(TAG, "Reverting backup queue - restaging everything");
+            for (BackupRequest request : mOriginalQueue) {
+                dataChangedImpl(request.packageName);
+            }
+            // We also want to reset the backup schedule based on whatever
+            // the transport suggests by way of retry/backoff time.
+            restartBackupAlarm();
+        }
+
+        void agentErrorCleanup() {
+            mBackupDataName.delete();
+            mNewStateName.delete();
+            clearAgentState();
+
+            executeNextState(mQueue.isEmpty() ? BackupState.FINAL : BackupState.RUNNING_QUEUE);
+        }
+
+        // Cleanup common to both success and failure cases
+        void clearAgentState() {
+            try { if (mSavedState != null) mSavedState.close(); } catch (IOException e) {}
+            try { if (mBackupData != null) mBackupData.close(); } catch (IOException e) {}
+            try { if (mNewState != null) mNewState.close(); } catch (IOException e) {}
+            mSavedState = mBackupData = mNewState = null;
+            synchronized (mCurrentOpLock) {
+                mCurrentOperations.clear();
+            }
+
+            // If this was a pseudopackage there's no associated Activity Manager state
+            if (mCurrentPackage.applicationInfo != null) {
+                try {  // unbind even on timeout, just in case
+                    mActivityManager.unbindBackupAgent(mCurrentPackage.applicationInfo);
+                } catch (RemoteException e) {}
+            }
+        }
+
+        void restartBackupAlarm() {
+            synchronized (mQueueLock) {
+                try {
+                    startBackupAlarmsLocked(mTransport.requestBackupTime());
+                } catch (RemoteException e) { /* cannot happen */ }
+            }
+        }
+
+        void executeNextState(BackupState nextState) {
+            if (MORE_DEBUG) Slog.i(TAG, " => executing next step on "
+                    + this + " nextState=" + nextState);
+            mCurrentState = nextState;
+            Message msg = mBackupHandler.obtainMessage(MSG_BACKUP_RESTORE_STEP, this);
+            mBackupHandler.sendMessage(msg);
         }
     }
 
@@ -1959,7 +2193,7 @@
                     }
 
                     if (DEBUG) Slog.d(TAG, "Calling doFullBackup() on " + mPackage.packageName);
-                    prepareOperationTimeout(mToken, TIMEOUT_FULL_BACKUP_INTERVAL);
+                    prepareOperationTimeout(mToken, TIMEOUT_FULL_BACKUP_INTERVAL, null);
                     mAgent.doFullBackup(mPipe, mToken, mBackupManagerBinder);
                 } catch (IOException e) {
                     Slog.e(TAG, "Error running full backup for " + mPackage.packageName);
@@ -2320,7 +2554,7 @@
                     sendOnBackupPackage("Shared storage");
 
                     final int token = generateToken();
-                    prepareOperationTimeout(token, TIMEOUT_SHARED_BACKUP_INTERVAL);
+                    prepareOperationTimeout(token, TIMEOUT_SHARED_BACKUP_INTERVAL, null);
                     agent.doFullBackup(mOutputFile, token, mBackupManagerBinder);
                     if (!waitUntilOperationComplete(token)) {
                         Slog.e(TAG, "Full backup failed on shared storage");
@@ -2899,8 +3133,7 @@
                             try {
                                 if (DEBUG) Slog.d(TAG, "Invoking agent to restore file "
                                         + info.path);
-                                prepareOperationTimeout(token,
-                                        TIMEOUT_FULL_BACKUP_INTERVAL);
+                                prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, null);
                                 // fire up the app's agent listening on the socket.  If
                                 // the agent is running in the system process we can't
                                 // just invoke it asynchronously, so we provide a thread
@@ -3693,7 +3926,15 @@
         return true;
     }
 
-    class PerformRestoreTask implements Runnable {
+    enum RestoreState {
+        INITIAL,
+        DOWNLOAD_DATA,
+        PM_METADATA,
+        RUNNING_QUEUE,
+        FINAL
+    }
+
+    class PerformRestoreTask implements BackupRestoreTask {
         private IBackupTransport mTransport;
         private IRestoreObserver mObserver;
         private long mToken;
@@ -3702,6 +3943,21 @@
         private int mPmToken;
         private boolean mNeedFullBackup;
         private HashSet<String> mFilterSet;
+        private long mStartRealtime;
+        private PackageManagerBackupAgent mPmAgent;
+        private List<PackageInfo> mAgentPackages;
+        private ArrayList<PackageInfo> mRestorePackages;
+        private RestoreState mCurrentState;
+        private int mCount;
+        private boolean mFinished;
+        private int mStatus;
+        private File mBackupDataName;
+        private File mNewStateName;
+        private File mSavedStateName;
+        private ParcelFileDescriptor mBackupData;
+        private ParcelFileDescriptor mNewState;
+        private PackageInfo mCurrentPackage;
+
 
         class RestoreRequest {
             public PackageInfo app;
@@ -3716,6 +3972,10 @@
         PerformRestoreTask(IBackupTransport transport, IRestoreObserver observer,
                 long restoreSetToken, PackageInfo targetPackage, int pmToken,
                 boolean needFullBackup, String[] filterSet) {
+            mCurrentState = RestoreState.INITIAL;
+            mFinished = false;
+            mPmAgent = null;
+
             mTransport = transport;
             mObserver = observer;
             mToken = restoreSetToken;
@@ -3739,50 +3999,79 @@
             }
         }
 
-        public void run() {
-            long startRealtime = SystemClock.elapsedRealtime();
-            if (DEBUG) Slog.v(TAG, "Beginning restore process mTransport=" + mTransport
-                    + " mObserver=" + mObserver + " mToken=" + Long.toHexString(mToken)
-                    + " mTargetPackage=" + mTargetPackage + " mFilterSet=" + mFilterSet
-                    + " mPmToken=" + mPmToken);
+        // Execute one tick of whatever state machine the task implements
+        @Override
+        public void execute() {
+            if (MORE_DEBUG) Slog.v(TAG, "*** Executing restore step: " + mCurrentState);
+            switch (mCurrentState) {
+                case INITIAL:
+                    beginRestore();
+                    break;
 
-            PackageManagerBackupAgent pmAgent = null;
-            int error = -1; // assume error
+                case DOWNLOAD_DATA:
+                    downloadRestoreData();
+                    break;
 
-            // build the set of apps to restore
+                case PM_METADATA:
+                    restorePmMetadata();
+                    break;
+
+                case RUNNING_QUEUE:
+                    restoreNextAgent();
+                    break;
+
+                case FINAL:
+                    if (!mFinished) finalizeRestore();
+                    else {
+                        Slog.e(TAG, "Duplicate finish");
+                    }
+                    mFinished = true;
+                    break;
+            }
+        }
+
+        // Initialize and set up for the PM metadata restore, which comes first
+        void beginRestore() {
+            // Don't account time doing the restore as inactivity of the app
+            // that has opened a restore session.
+            mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT);
+
+            // Assume error until we successfully init everything
+            mStatus = BackupConstants.TRANSPORT_ERROR;
+
             try {
                 // TODO: Log this before getAvailableRestoreSets, somehow
                 EventLog.writeEvent(EventLogTags.RESTORE_START, mTransport.transportDirName(), mToken);
 
                 // Get the list of all packages which have backup enabled.
                 // (Include the Package Manager metadata pseudo-package first.)
-                ArrayList<PackageInfo> restorePackages = new ArrayList<PackageInfo>();
+                mRestorePackages = new ArrayList<PackageInfo>();
                 PackageInfo omPackage = new PackageInfo();
                 omPackage.packageName = PACKAGE_MANAGER_SENTINEL;
-                restorePackages.add(omPackage);
+                mRestorePackages.add(omPackage);
 
-                List<PackageInfo> agentPackages = allAgentPackages();
+                mAgentPackages = allAgentPackages();
                 if (mTargetPackage == null) {
                     // if there's a filter set, strip out anything that isn't
                     // present before proceeding
                     if (mFilterSet != null) {
-                        for (int i = agentPackages.size() - 1; i >= 0; i--) {
-                            final PackageInfo pkg = agentPackages.get(i);
+                        for (int i = mAgentPackages.size() - 1; i >= 0; i--) {
+                            final PackageInfo pkg = mAgentPackages.get(i);
                             if (! mFilterSet.contains(pkg.packageName)) {
-                                agentPackages.remove(i);
+                                mAgentPackages.remove(i);
                             }
                         }
-                        if (DEBUG) {
+                        if (MORE_DEBUG) {
                             Slog.i(TAG, "Post-filter package set for restore:");
-                            for (PackageInfo p : agentPackages) {
+                            for (PackageInfo p : mAgentPackages) {
                                 Slog.i(TAG, "    " + p);
                             }
                         }
                     }
-                    restorePackages.addAll(agentPackages);
+                    mRestorePackages.addAll(mAgentPackages);
                 } else {
                     // Just one package to attempt restore of
-                    restorePackages.add(mTargetPackage);
+                    mRestorePackages.add(mTargetPackage);
                 }
 
                 // let the observer know that we're running
@@ -3790,306 +4079,412 @@
                     try {
                         // !!! TODO: get an actual count from the transport after
                         // its startRestore() runs?
-                        mObserver.restoreStarting(restorePackages.size());
+                        mObserver.restoreStarting(mRestorePackages.size());
                     } catch (RemoteException e) {
                         Slog.d(TAG, "Restore observer died at restoreStarting");
                         mObserver = null;
                     }
                 }
+            } catch (RemoteException e) {
+                // Something has gone catastrophically wrong with the transport
+                Slog.e(TAG, "Error communicating with transport for restore");
+                executeNextState(RestoreState.FINAL);
+                return;
+            }
 
-                if (mTransport.startRestore(mToken, restorePackages.toArray(new PackageInfo[0])) !=
-                        BackupConstants.TRANSPORT_OK) {
+            mStatus = BackupConstants.TRANSPORT_OK;
+            executeNextState(RestoreState.DOWNLOAD_DATA);
+        }
+
+        void downloadRestoreData() {
+            // Note that the download phase can be very time consuming, but we're executing
+            // it inline here on the looper.  This is "okay" because it is not calling out to
+            // third party code; the transport is "trusted," and so we assume it is being a
+            // good citizen and timing out etc when appropriate.
+            //
+            // TODO: when appropriate, move the download off the looper and rearrange the
+            //       error handling around that.
+            try {
+                mStatus = mTransport.startRestore(mToken,
+                        mRestorePackages.toArray(new PackageInfo[0]));
+                if (mStatus != BackupConstants.TRANSPORT_OK) {
                     Slog.e(TAG, "Error starting restore operation");
                     EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+                    executeNextState(RestoreState.FINAL);
                     return;
                 }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error communicating with transport for restore");
+                EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+                mStatus = BackupConstants.TRANSPORT_ERROR;
+                executeNextState(RestoreState.FINAL);
+                return;
+            }
 
+            // Successful download of the data to be parceled out to the apps, so off we go.
+            executeNextState(RestoreState.PM_METADATA);
+        }
+
+        void restorePmMetadata() {
+            try {
                 String packageName = mTransport.nextRestorePackage();
                 if (packageName == null) {
                     Slog.e(TAG, "Error getting first restore package");
                     EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+                    mStatus = BackupConstants.TRANSPORT_ERROR;
+                    executeNextState(RestoreState.FINAL);
                     return;
                 } else if (packageName.equals("")) {
                     Slog.i(TAG, "No restore data available");
-                    int millis = (int) (SystemClock.elapsedRealtime() - startRealtime);
+                    int millis = (int) (SystemClock.elapsedRealtime() - mStartRealtime);
                     EventLog.writeEvent(EventLogTags.RESTORE_SUCCESS, 0, millis);
+                    mStatus = BackupConstants.TRANSPORT_OK;
+                    executeNextState(RestoreState.FINAL);
                     return;
                 } else if (!packageName.equals(PACKAGE_MANAGER_SENTINEL)) {
                     Slog.e(TAG, "Expected restore data for \"" + PACKAGE_MANAGER_SENTINEL
-                          + "\", found only \"" + packageName + "\"");
+                            + "\", found only \"" + packageName + "\"");
                     EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, PACKAGE_MANAGER_SENTINEL,
                             "Package manager data missing");
+                    executeNextState(RestoreState.FINAL);
                     return;
                 }
 
                 // Pull the Package Manager metadata from the restore set first
-                pmAgent = new PackageManagerBackupAgent(
-                        mPackageManager, agentPackages);
-                processOneRestore(omPackage, 0, IBackupAgent.Stub.asInterface(pmAgent.onBind()),
+                PackageInfo omPackage = new PackageInfo();
+                omPackage.packageName = PACKAGE_MANAGER_SENTINEL;
+                mPmAgent = new PackageManagerBackupAgent(
+                        mPackageManager, mAgentPackages);
+                initiateOneRestore(omPackage, 0, IBackupAgent.Stub.asInterface(mPmAgent.onBind()),
                         mNeedFullBackup);
+                // The PM agent called operationComplete() already, because our invocation
+                // of it is process-local and therefore synchronous.  That means that a
+                // RUNNING_QUEUE message is already enqueued.  Only if we're unable to
+                // proceed with running the queue do we remove that pending message and
+                // jump straight to the FINAL state.
 
                 // Verify that the backup set includes metadata.  If not, we can't do
                 // signature/version verification etc, so we simply do not proceed with
                 // the restore operation.
-                if (!pmAgent.hasMetadata()) {
+                if (!mPmAgent.hasMetadata()) {
                     Slog.e(TAG, "No restore metadata available, so not restoring settings");
                     EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, PACKAGE_MANAGER_SENTINEL,
-                            "Package manager restore metadata missing");
+                    "Package manager restore metadata missing");
+                    mStatus = BackupConstants.TRANSPORT_ERROR;
+                    mBackupHandler.removeMessages(MSG_BACKUP_RESTORE_STEP, this);
+                    executeNextState(RestoreState.FINAL);
                     return;
                 }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error communicating with transport for restore");
+                EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+                mStatus = BackupConstants.TRANSPORT_ERROR;
+                mBackupHandler.removeMessages(MSG_BACKUP_RESTORE_STEP, this);
+                executeNextState(RestoreState.FINAL);
+                return;
+            }
 
-                int count = 0;
-                for (;;) {
-                    packageName = mTransport.nextRestorePackage();
+            // Metadata is intact, so we can now run the restore queue.  If we get here,
+            // we have already enqueued the necessary next-step message on the looper.
+        }
 
-                    if (packageName == null) {
-                        Slog.e(TAG, "Error getting next restore package");
-                        EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
-                        return;
-                    } else if (packageName.equals("")) {
-                        if (DEBUG) Slog.v(TAG, "No next package, finishing restore");
-                        break;
-                    }
+        void restoreNextAgent() {
+            try {
+                String packageName = mTransport.nextRestorePackage();
 
-                    if (mObserver != null) {
-                        try {
-                            mObserver.onUpdate(count, packageName);
-                        } catch (RemoteException e) {
-                            Slog.d(TAG, "Restore observer died in onUpdate");
-                            mObserver = null;
-                        }
-                    }
-
-                    Metadata metaInfo = pmAgent.getRestoredMetadata(packageName);
-                    if (metaInfo == null) {
-                        Slog.e(TAG, "Missing metadata for " + packageName);
-                        EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
-                                "Package metadata missing");
-                        continue;
-                    }
-
-                    PackageInfo packageInfo;
-                    try {
-                        int flags = PackageManager.GET_SIGNATURES;
-                        packageInfo = mPackageManager.getPackageInfo(packageName, flags);
-                    } catch (NameNotFoundException e) {
-                        Slog.e(TAG, "Invalid package restoring data", e);
-                        EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
-                                "Package missing on device");
-                        continue;
-                    }
-
-                    if (metaInfo.versionCode > packageInfo.versionCode) {
-                        // Data is from a "newer" version of the app than we have currently
-                        // installed.  If the app has not declared that it is prepared to
-                        // handle this case, we do not attempt the restore.
-                        if ((packageInfo.applicationInfo.flags
-                                & ApplicationInfo.FLAG_RESTORE_ANY_VERSION) == 0) {
-                            String message = "Version " + metaInfo.versionCode
-                                    + " > installed version " + packageInfo.versionCode;
-                            Slog.w(TAG, "Package " + packageName + ": " + message);
-                            EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
-                                    packageName, message);
-                            continue;
-                        } else {
-                            if (DEBUG) Slog.v(TAG, "Version " + metaInfo.versionCode
-                                    + " > installed " + packageInfo.versionCode
-                                    + " but restoreAnyVersion");
-                        }
-                    }
-
-                    if (!signaturesMatch(metaInfo.signatures, packageInfo)) {
-                        Slog.w(TAG, "Signature mismatch restoring " + packageName);
-                        EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
-                                "Signature mismatch");
-                        continue;
-                    }
-
-                    if (DEBUG) Slog.v(TAG, "Package " + packageName
-                            + " restore version [" + metaInfo.versionCode
-                            + "] is compatible with installed version ["
-                            + packageInfo.versionCode + "]");
-
-                    // Then set up and bind the agent
-                    IBackupAgent agent = bindToAgentSynchronous(
-                            packageInfo.applicationInfo,
-                            IApplicationThread.BACKUP_MODE_INCREMENTAL);
-                    if (agent == null) {
-                        Slog.w(TAG, "Can't find backup agent for " + packageName);
-                        EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
-                                "Restore agent missing");
-                        continue;
-                    }
-
-                    // And then finally run the restore on this agent
-                    try {
-                        processOneRestore(packageInfo, metaInfo.versionCode, agent,
-                                mNeedFullBackup);
-                        ++count;
-                    } finally {
-                        // unbind and tidy up even on timeout or failure, just in case
-                        mActivityManager.unbindBackupAgent(packageInfo.applicationInfo);
-
-                        // The agent was probably running with a stub Application object,
-                        // which isn't a valid run mode for the main app logic.  Shut
-                        // down the app so that next time it's launched, it gets the
-                        // usual full initialization.  Note that this is only done for
-                        // full-system restores: when a single app has requested a restore,
-                        // it is explicitly not killed following that operation.
-                        if (mTargetPackage == null && (packageInfo.applicationInfo.flags
-                                & ApplicationInfo.FLAG_KILL_AFTER_RESTORE) != 0) {
-                            if (DEBUG) Slog.d(TAG, "Restore complete, killing host process of "
-                                    + packageInfo.applicationInfo.processName);
-                            mActivityManager.killApplicationProcess(
-                                    packageInfo.applicationInfo.processName,
-                                    packageInfo.applicationInfo.uid);
-                        }
-                    }
-                }
-
-                // if we get this far, report success to the observer
-                error = 0;
-                int millis = (int) (SystemClock.elapsedRealtime() - startRealtime);
-                EventLog.writeEvent(EventLogTags.RESTORE_SUCCESS, count, millis);
-            } catch (Exception e) {
-                Slog.e(TAG, "Error in restore thread", e);
-            } finally {
-                if (DEBUG) Slog.d(TAG, "finishing restore mObserver=" + mObserver);
-
-                try {
-                    mTransport.finishRestore();
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Error finishing restore", e);
+                if (packageName == null) {
+                    Slog.e(TAG, "Error getting next restore package");
+                    EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+                    executeNextState(RestoreState.FINAL);
+                    return;
+                } else if (packageName.equals("")) {
+                    if (DEBUG) Slog.v(TAG, "No next package, finishing restore");
+                    int millis = (int) (SystemClock.elapsedRealtime() - mStartRealtime);
+                    EventLog.writeEvent(EventLogTags.RESTORE_SUCCESS, mCount, millis);
+                    executeNextState(RestoreState.FINAL);
+                    return;
                 }
 
                 if (mObserver != null) {
                     try {
-                        mObserver.restoreFinished(error);
+                        mObserver.onUpdate(mCount, packageName);
                     } catch (RemoteException e) {
-                        Slog.d(TAG, "Restore observer died at restoreFinished");
+                        Slog.d(TAG, "Restore observer died in onUpdate");
+                        mObserver = null;
                     }
                 }
 
-                // If this was a restoreAll operation, record that this was our
-                // ancestral dataset, as well as the set of apps that are possibly
-                // restoreable from the dataset
-                if (mTargetPackage == null && pmAgent != null) {
-                    mAncestralPackages = pmAgent.getRestoredPackages();
-                    mAncestralToken = mToken;
-                    writeRestoreTokens();
+                Metadata metaInfo = mPmAgent.getRestoredMetadata(packageName);
+                if (metaInfo == null) {
+                    Slog.e(TAG, "Missing metadata for " + packageName);
+                    EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
+                            "Package metadata missing");
+                    executeNextState(RestoreState.RUNNING_QUEUE);
+                    return;
                 }
 
-                // We must under all circumstances tell the Package Manager to
-                // proceed with install notifications if it's waiting for us.
-                if (mPmToken > 0) {
-                    if (DEBUG) Slog.v(TAG, "finishing PM token " + mPmToken);
-                    try {
-                        mPackageManagerBinder.finishPackageInstall(mPmToken);
-                    } catch (RemoteException e) { /* can't happen */ }
+                PackageInfo packageInfo;
+                try {
+                    int flags = PackageManager.GET_SIGNATURES;
+                    packageInfo = mPackageManager.getPackageInfo(packageName, flags);
+                } catch (NameNotFoundException e) {
+                    Slog.e(TAG, "Invalid package restoring data", e);
+                    EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
+                            "Package missing on device");
+                    executeNextState(RestoreState.RUNNING_QUEUE);
+                    return;
                 }
 
-                // Furthermore we need to reset the session timeout clock
-                mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT);
-                mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_TIMEOUT,
-                        TIMEOUT_RESTORE_INTERVAL);
+                if (metaInfo.versionCode > packageInfo.versionCode) {
+                    // Data is from a "newer" version of the app than we have currently
+                    // installed.  If the app has not declared that it is prepared to
+                    // handle this case, we do not attempt the restore.
+                    if ((packageInfo.applicationInfo.flags
+                            & ApplicationInfo.FLAG_RESTORE_ANY_VERSION) == 0) {
+                        String message = "Version " + metaInfo.versionCode
+                        + " > installed version " + packageInfo.versionCode;
+                        Slog.w(TAG, "Package " + packageName + ": " + message);
+                        EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
+                                packageName, message);
+                        executeNextState(RestoreState.RUNNING_QUEUE);
+                        return;
+                    } else {
+                        if (DEBUG) Slog.v(TAG, "Version " + metaInfo.versionCode
+                                + " > installed " + packageInfo.versionCode
+                                + " but restoreAnyVersion");
+                    }
+                }
 
-                // done; we can finally release the wakelock
-                mWakelock.release();
+                if (!signaturesMatch(metaInfo.signatures, packageInfo)) {
+                    Slog.w(TAG, "Signature mismatch restoring " + packageName);
+                    EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
+                            "Signature mismatch");
+                    executeNextState(RestoreState.RUNNING_QUEUE);
+                    return;
+                }
+
+                if (DEBUG) Slog.v(TAG, "Package " + packageName
+                        + " restore version [" + metaInfo.versionCode
+                        + "] is compatible with installed version ["
+                        + packageInfo.versionCode + "]");
+
+                // Then set up and bind the agent
+                IBackupAgent agent = bindToAgentSynchronous(
+                        packageInfo.applicationInfo,
+                        IApplicationThread.BACKUP_MODE_INCREMENTAL);
+                if (agent == null) {
+                    Slog.w(TAG, "Can't find backup agent for " + packageName);
+                    EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
+                            "Restore agent missing");
+                    executeNextState(RestoreState.RUNNING_QUEUE);
+                    return;
+                }
+
+                // And then finally start the restore on this agent
+                try {
+                    initiateOneRestore(packageInfo, metaInfo.versionCode, agent, mNeedFullBackup);
+                    ++mCount;
+                } catch (Exception e) {
+                    Slog.e(TAG, "Error when attempting restore: " + e.toString());
+                    agentErrorCleanup();
+                    executeNextState(RestoreState.RUNNING_QUEUE);
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Unable to fetch restore data from transport");
+                mStatus = BackupConstants.TRANSPORT_ERROR;
+                executeNextState(RestoreState.FINAL);
             }
         }
 
-        // Do the guts of a restore of one application, using mTransport.getRestoreData().
-        void processOneRestore(PackageInfo app, int appVersionCode, IBackupAgent agent,
+        void finalizeRestore() {
+            if (MORE_DEBUG) Slog.d(TAG, "finishing restore mObserver=" + mObserver);
+
+            try {
+                mTransport.finishRestore();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error finishing restore", e);
+            }
+
+            if (mObserver != null) {
+                try {
+                    mObserver.restoreFinished(mStatus);
+                } catch (RemoteException e) {
+                    Slog.d(TAG, "Restore observer died at restoreFinished");
+                }
+            }
+
+            // If this was a restoreAll operation, record that this was our
+            // ancestral dataset, as well as the set of apps that are possibly
+            // restoreable from the dataset
+            if (mTargetPackage == null && mPmAgent != null) {
+                mAncestralPackages = mPmAgent.getRestoredPackages();
+                mAncestralToken = mToken;
+                writeRestoreTokens();
+            }
+
+            // We must under all circumstances tell the Package Manager to
+            // proceed with install notifications if it's waiting for us.
+            if (mPmToken > 0) {
+                if (MORE_DEBUG) Slog.v(TAG, "finishing PM token " + mPmToken);
+                try {
+                    mPackageManagerBinder.finishPackageInstall(mPmToken);
+                } catch (RemoteException e) { /* can't happen */ }
+            }
+
+            // Furthermore we need to reset the session timeout clock
+            mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT);
+            mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_TIMEOUT,
+                    TIMEOUT_RESTORE_INTERVAL);
+
+            // done; we can finally release the wakelock
+            Slog.i(TAG, "Restore complete.");
+            mWakelock.release();
+        }
+
+        // Call asynchronously into the app, passing it the restore data.  The next step
+        // after this is always a callback, either operationComplete() or handleTimeout().
+        void initiateOneRestore(PackageInfo app, int appVersionCode, IBackupAgent agent,
                 boolean needFullBackup) {
-            // !!! TODO: actually run the restore through mTransport
+            mCurrentPackage = app;
             final String packageName = app.packageName;
 
-            if (DEBUG) Slog.d(TAG, "processOneRestore packageName=" + packageName);
+            if (DEBUG) Slog.d(TAG, "initiateOneRestore packageName=" + packageName);
 
             // !!! TODO: get the dirs from the transport
-            File backupDataName = new File(mDataDir, packageName + ".restore");
-            File newStateName = new File(mStateDir, packageName + ".new");
-            File savedStateName = new File(mStateDir, packageName);
-
-            ParcelFileDescriptor backupData = null;
-            ParcelFileDescriptor newState = null;
+            mBackupDataName = new File(mDataDir, packageName + ".restore");
+            mNewStateName = new File(mStateDir, packageName + ".new");
+            mSavedStateName = new File(mStateDir, packageName);
 
             final int token = generateToken();
             try {
                 // Run the transport's restore pass
-                backupData = ParcelFileDescriptor.open(backupDataName,
+                mBackupData = ParcelFileDescriptor.open(mBackupDataName,
                             ParcelFileDescriptor.MODE_READ_WRITE |
                             ParcelFileDescriptor.MODE_CREATE |
                             ParcelFileDescriptor.MODE_TRUNCATE);
 
-                if (mTransport.getRestoreData(backupData) != BackupConstants.TRANSPORT_OK) {
+                if (mTransport.getRestoreData(mBackupData) != BackupConstants.TRANSPORT_OK) {
                     Slog.e(TAG, "Error getting restore data for " + packageName);
                     EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
                     return;
                 }
 
                 // Okay, we have the data.  Now have the agent do the restore.
-                backupData.close();
-                backupData = ParcelFileDescriptor.open(backupDataName,
+                mBackupData.close();
+                mBackupData = ParcelFileDescriptor.open(mBackupDataName,
                             ParcelFileDescriptor.MODE_READ_ONLY);
 
-                newState = ParcelFileDescriptor.open(newStateName,
+                mNewState = ParcelFileDescriptor.open(mNewStateName,
                             ParcelFileDescriptor.MODE_READ_WRITE |
                             ParcelFileDescriptor.MODE_CREATE |
                             ParcelFileDescriptor.MODE_TRUNCATE);
 
                 // Kick off the restore, checking for hung agents
-                prepareOperationTimeout(token, TIMEOUT_RESTORE_INTERVAL);
-                agent.doRestore(backupData, appVersionCode, newState, token, mBackupManagerBinder);
-                boolean success = waitUntilOperationComplete(token);
-
-                if (!success) {
-                    throw new RuntimeException("restore timeout");
-                }
-
-                // if everything went okay, remember the recorded state now
-                //
-                // !!! TODO: the restored data should be migrated on the server
-                // side into the current dataset.  In that case the new state file
-                // we just created would reflect the data already extant in the
-                // backend, so there'd be nothing more to do.  Until that happens,
-                // however, we need to make sure that we record the data to the
-                // current backend dataset.  (Yes, this means shipping the data over
-                // the wire in both directions.  That's bad, but consistency comes
-                // first, then efficiency.)  Once we introduce server-side data
-                // migration to the newly-restored device's dataset, we will change
-                // the following from a discard of the newly-written state to the
-                // "correct" operation of renaming into the canonical state blob.
-                newStateName.delete();                      // TODO: remove; see above comment
-                //newStateName.renameTo(savedStateName);    // TODO: replace with this
-
-                int size = (int) backupDataName.length();
-                EventLog.writeEvent(EventLogTags.RESTORE_PACKAGE, packageName, size);
+                prepareOperationTimeout(token, TIMEOUT_RESTORE_INTERVAL, this);
+                agent.doRestore(mBackupData, appVersionCode, mNewState, token, mBackupManagerBinder);
             } catch (Exception e) {
-                Slog.e(TAG, "Error restoring data for " + packageName, e);
+                Slog.e(TAG, "Unable to call app for restore: " + packageName, e);
                 EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName, e.toString());
+                agentErrorCleanup();    // clears any pending timeout messages as well
 
-                // If the agent fails restore, it might have put the app's data
-                // into an incoherent state.  For consistency we wipe its data
-                // again in this case before propagating the exception
-                clearApplicationDataSynchronous(packageName);
-            } finally {
-                backupDataName.delete();
-                try { if (backupData != null) backupData.close(); } catch (IOException e) {}
-                try { if (newState != null) newState.close(); } catch (IOException e) {}
-                backupData = newState = null;
-                synchronized (mCurrentOperations) {
-                    mCurrentOperations.delete(token);
-                }
+                // After a restore failure we go back to running the queue.  If there
+                // are no more packages to be restored that will be handled by the
+                // next step.
+                executeNextState(RestoreState.RUNNING_QUEUE);
+            }
+        }
 
-                // If we know a priori that we'll need to perform a full post-restore backup
-                // pass, clear the new state file data.  This means we're discarding work that
-                // was just done by the app's agent, but this way the agent doesn't need to
-                // take any special action based on global device state.
-                if (needFullBackup) {
-                    newStateName.delete();
+        void agentErrorCleanup() {
+            // If the agent fails restore, it might have put the app's data
+            // into an incoherent state.  For consistency we wipe its data
+            // again in this case before continuing with normal teardown
+            clearApplicationDataSynchronous(mCurrentPackage.packageName);
+            agentCleanup();
+        }
+
+        void agentCleanup() {
+            mBackupDataName.delete();
+            try { if (mBackupData != null) mBackupData.close(); } catch (IOException e) {}
+            try { if (mNewState != null) mNewState.close(); } catch (IOException e) {}
+            mBackupData = mNewState = null;
+
+            // if everything went okay, remember the recorded state now
+            //
+            // !!! TODO: the restored data should be migrated on the server
+            // side into the current dataset.  In that case the new state file
+            // we just created would reflect the data already extant in the
+            // backend, so there'd be nothing more to do.  Until that happens,
+            // however, we need to make sure that we record the data to the
+            // current backend dataset.  (Yes, this means shipping the data over
+            // the wire in both directions.  That's bad, but consistency comes
+            // first, then efficiency.)  Once we introduce server-side data
+            // migration to the newly-restored device's dataset, we will change
+            // the following from a discard of the newly-written state to the
+            // "correct" operation of renaming into the canonical state blob.
+            mNewStateName.delete();                      // TODO: remove; see above comment
+            //mNewStateName.renameTo(mSavedStateName);   // TODO: replace with this
+
+            // If this wasn't the PM pseudopackage, tear down the agent side
+            if (mCurrentPackage.applicationInfo != null) {
+                // unbind and tidy up even on timeout or failure
+                try {
+                    mActivityManager.unbindBackupAgent(mCurrentPackage.applicationInfo);
+
+                    // The agent was probably running with a stub Application object,
+                    // which isn't a valid run mode for the main app logic.  Shut
+                    // down the app so that next time it's launched, it gets the
+                    // usual full initialization.  Note that this is only done for
+                    // full-system restores: when a single app has requested a restore,
+                    // it is explicitly not killed following that operation.
+                    if (mTargetPackage == null && (mCurrentPackage.applicationInfo.flags
+                            & ApplicationInfo.FLAG_KILL_AFTER_RESTORE) != 0) {
+                        if (DEBUG) Slog.d(TAG, "Restore complete, killing host process of "
+                                + mCurrentPackage.applicationInfo.processName);
+                        mActivityManager.killApplicationProcess(
+                                mCurrentPackage.applicationInfo.processName,
+                                mCurrentPackage.applicationInfo.uid);
+                    }
+                } catch (RemoteException e) {
+                    // can't happen; we run in the same process as the activity manager
                 }
             }
+
+            // The caller is responsible for reestablishing the state machine; our
+            // responsibility here is to clear the decks for whatever comes next.
+            mBackupHandler.removeMessages(MSG_TIMEOUT, this);
+            synchronized (mCurrentOpLock) {
+                mCurrentOperations.clear();
+            }
+        }
+
+        // A call to agent.doRestore() has been positively acknowledged as complete
+        @Override
+        public void operationComplete() {
+            int size = (int) mBackupDataName.length();
+            EventLog.writeEvent(EventLogTags.RESTORE_PACKAGE, mCurrentPackage.packageName, size);
+            // Just go back to running the restore queue
+            agentCleanup();
+
+            executeNextState(RestoreState.RUNNING_QUEUE);
+        }
+
+        // A call to agent.doRestore() has timed out
+        @Override
+        public void handleTimeout() {
+            Slog.e(TAG, "Timeout restoring application " + mCurrentPackage.packageName);
+            EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
+                    mCurrentPackage.packageName, "restore timeout");
+            // Handle like an agent that threw on invocation: wipe it and go on to the next
+            agentErrorCleanup();
+            executeNextState(RestoreState.RUNNING_QUEUE);
+        }
+
+        void executeNextState(RestoreState nextState) {
+            if (MORE_DEBUG) Slog.i(TAG, " => executing next step on "
+                    + this + " nextState=" + nextState);
+            mCurrentState = nextState;
+            Message msg = mBackupHandler.obtainMessage(MSG_BACKUP_RESTORE_STEP, this);
+            mBackupHandler.sendMessage(msg);
         }
     }
 
@@ -4884,12 +5279,23 @@
 
     // Note that a currently-active backup agent has notified us that it has
     // completed the given outstanding asynchronous backup/restore operation.
+    @Override
     public void opComplete(int token) {
+        if (MORE_DEBUG) Slog.v(TAG, "opComplete: " + Integer.toHexString(token));
+        Operation op = null;
         synchronized (mCurrentOpLock) {
-            if (MORE_DEBUG) Slog.v(TAG, "opComplete: " + Integer.toHexString(token));
-            mCurrentOperations.put(token, OP_ACKNOWLEDGED);
+            op = mCurrentOperations.get(token);
+            if (op != null) {
+                op.state = OP_ACKNOWLEDGED;
+            }
             mCurrentOpLock.notifyAll();
         }
+
+        // The completion callback, if any, is invoked on the handler
+        if (op != null && op.callback != null) {
+            Message msg = mBackupHandler.obtainMessage(MSG_OP_COMPLETE, op.callback);
+            mBackupHandler.sendMessage(msg);
+        }
     }
 
     // ----- Restore session -----
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index 1497511..f774dea 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -98,6 +98,7 @@
     public static final String LIMIT_GLOBAL_ALERT = "globalAlert";
 
     /** {@link #mStatsXtUid} headers. */
+    private static final String KEY_IDX = "idx";
     private static final String KEY_IFACE = "iface";
     private static final String KEY_UID = "uid_tag_int";
     private static final String KEY_COUNTER_SET = "cnt_set";
@@ -1323,17 +1324,20 @@
 
         // TODO: remove knownLines check once 5087722 verified
         final HashSet<String> knownLines = Sets.newHashSet();
+        // TODO: remove lastIdx check once 5270106 verified
+        int lastIdx = 0;
 
         final ArrayList<String> keys = Lists.newArrayList();
         final ArrayList<String> values = Lists.newArrayList();
         final HashMap<String, String> parsed = Maps.newHashMap();
 
         BufferedReader reader = null;
+        String line = null;
         try {
             reader = new BufferedReader(new FileReader(mStatsXtUid));
 
             // parse first line as header
-            String line = reader.readLine();
+            line = reader.readLine();
             splitLine(line, keys);
 
             // parse remaining lines
@@ -1342,32 +1346,35 @@
                 parseLine(keys, values, parsed);
 
                 if (!knownLines.add(line)) {
-                    throw new IllegalStateException("encountered duplicate proc entry");
+                    throw new IllegalStateException("duplicate proc entry: " + line);
                 }
 
-                try {
-                    entry.iface = parsed.get(KEY_IFACE);
-                    entry.uid = getParsedInt(parsed, KEY_UID);
-                    entry.set = getParsedInt(parsed, KEY_COUNTER_SET);
-                    entry.tag = kernelToTag(parsed.get(KEY_TAG_HEX));
-                    entry.rxBytes = getParsedLong(parsed, KEY_RX_BYTES);
-                    entry.rxPackets = getParsedLong(parsed, KEY_RX_PACKETS);
-                    entry.txBytes = getParsedLong(parsed, KEY_TX_BYTES);
-                    entry.txPackets = getParsedLong(parsed, KEY_TX_PACKETS);
+                final int idx = getParsedInt(parsed, KEY_IDX);
+                if (idx > lastIdx + 1) {
+                    throw new IllegalStateException(
+                            "inconsistent idx=" + idx + " after lastIdx=" + lastIdx);
+                }
+                lastIdx = idx;
 
-                    if (limitUid == UID_ALL || limitUid == entry.uid) {
-                        stats.addValues(entry);
-                    }
-                } catch (NumberFormatException e) {
-                    Slog.w(TAG, "problem parsing stats row '" + line + "': " + e);
+                entry.iface = parsed.get(KEY_IFACE);
+                entry.uid = getParsedInt(parsed, KEY_UID);
+                entry.set = getParsedInt(parsed, KEY_COUNTER_SET);
+                entry.tag = kernelToTag(parsed.get(KEY_TAG_HEX));
+                entry.rxBytes = getParsedLong(parsed, KEY_RX_BYTES);
+                entry.rxPackets = getParsedLong(parsed, KEY_RX_PACKETS);
+                entry.txBytes = getParsedLong(parsed, KEY_TX_BYTES);
+                entry.txPackets = getParsedLong(parsed, KEY_TX_PACKETS);
+
+                if (limitUid == UID_ALL || limitUid == entry.uid) {
+                    stats.addValues(entry);
                 }
             }
         } catch (NullPointerException e) {
-            throw new IllegalStateException("problem parsing stats: " + e);
+            throw new IllegalStateException("problem parsing line: " + line, e);
         } catch (NumberFormatException e) {
-            throw new IllegalStateException("problem parsing stats: " + e);
+            throw new IllegalStateException("problem parsing line: " + line, e);
         } catch (IOException e) {
-            throw new IllegalStateException("problem parsing stats: " + e);
+            throw new IllegalStateException("problem parsing line: " + line, e);
         } finally {
             IoUtils.closeQuietly(reader);
         }
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index a80a2b8..e6b5898 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -37,7 +37,7 @@
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiWatchdogStateMachine;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
-import android.net.wifi.WpsConfiguration;
+import android.net.wifi.Wps;
 import android.net.wifi.WpsResult;
 import android.net.ConnectivityManager;
 import android.net.DhcpInfo;
@@ -286,7 +286,7 @@
                 }
                 case WifiManager.CMD_START_WPS: {
                     //replyTo has the original source
-                    mWifiStateMachine.startWps(msg.replyTo, (WpsConfiguration)msg.obj);
+                    mWifiStateMachine.startWps(msg.replyTo, (Wps)msg.obj);
                     break;
                 }
                 case WifiManager.CMD_DISABLE_NETWORK: {
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index 8af90ff..9067fae 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -100,6 +100,7 @@
 import android.telephony.TelephonyManager;
 import android.text.format.Formatter;
 import android.text.format.Time;
+import android.util.Log;
 import android.util.NtpTrustedTime;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -298,18 +299,9 @@
 
         try {
             mActivityManager.registerProcessObserver(mProcessObserver);
-        } catch (RemoteException e) {
-            // ouch, no foregroundActivities updates means some processes may
-            // never get network access.
-            Slog.e(TAG, "unable to register IProcessObserver", e);
-        }
-
-        try {
             mNetworkManager.registerObserver(mAlertObserver);
         } catch (RemoteException e) {
-            // ouch, no alert updates means we fall back to
-            // ACTION_NETWORK_STATS_UPDATED broadcasts.
-            Slog.e(TAG, "unable to register INetworkManagementEventObserver", e);
+            // ignored; both services live in system_server
         }
 
         // TODO: traverse existing processes to know foreground state, or have
@@ -462,7 +454,7 @@
                         // caused alert to trigger.
                         mNetworkStats.forceUpdate();
                     } catch (RemoteException e) {
-                        Slog.w(TAG, "problem updating network stats");
+                        // ignored; service lives in system_server
                     }
 
                     updateNetworkEnabledLocked();
@@ -495,9 +487,7 @@
 
             final long start = computeLastCycleBoundary(currentTime, policy);
             final long end = currentTime;
-
             final long totalBytes = getTotalBytes(policy.template, start, end);
-            if (totalBytes == UNKNOWN_BYTES) continue;
 
             if (policy.limitBytes != LIMIT_DISABLED && totalBytes >= policy.limitBytes) {
                 if (policy.lastSnooze >= start) {
@@ -671,7 +661,7 @@
                     packageName, tag, 0x0, builder.getNotification(), idReceived);
             mActiveNotifs.add(tag);
         } catch (RemoteException e) {
-            Slog.w(TAG, "problem during enqueueNotification: " + e);
+            // ignored; service lives in system_server
         }
     }
 
@@ -705,7 +695,7 @@
                     0x0, builder.getNotification(), idReceived);
             mActiveNotifs.add(tag);
         } catch (RemoteException e) {
-            Slog.w(TAG, "problem during enqueueNotification: " + e);
+            // ignored; service lives in system_server
         }
     }
 
@@ -716,7 +706,7 @@
             mNotifManager.cancelNotificationWithTag(
                     packageName, tag, 0x0);
         } catch (RemoteException e) {
-            Slog.w(TAG, "problem during enqueueNotification: " + e);
+            // ignored; service lives in system_server
         }
     }
 
@@ -758,9 +748,7 @@
 
             final long start = computeLastCycleBoundary(currentTime, policy);
             final long end = currentTime;
-
             final long totalBytes = getTotalBytes(policy.template, start, end);
-            if (totalBytes == UNKNOWN_BYTES) continue;
 
             // disable data connection when over limit and not snoozed
             final boolean overLimit = policy.limitBytes != LIMIT_DISABLED
@@ -810,7 +798,7 @@
         try {
             states = mConnManager.getAllNetworkState();
         } catch (RemoteException e) {
-            Slog.w(TAG, "problem reading network state");
+            // ignored; service lives in system_server
             return;
         }
 
@@ -857,9 +845,7 @@
 
             final long start = computeLastCycleBoundary(currentTime, policy);
             final long end = currentTime;
-
             final long totalBytes = getTotalBytes(policy.template, start, end);
-            if (totalBytes == UNKNOWN_BYTES) continue;
 
             if (LOGD) {
                 Slog.d(TAG, "applying policy " + policy.toString() + " to ifaces "
@@ -1006,9 +992,9 @@
             // missing policy is okay, probably first boot
             upgradeLegacyBackgroundData();
         } catch (IOException e) {
-            Slog.e(TAG, "problem reading network stats", e);
+            Log.wtf(TAG, "problem reading network policy", e);
         } catch (XmlPullParserException e) {
-            Slog.e(TAG, "problem reading network stats", e);
+            Log.wtf(TAG, "problem reading network policy", e);
         } finally {
             IoUtils.closeQuietly(fis);
         }
@@ -1246,12 +1232,10 @@
 
         final long currentTime = currentTimeMillis(false);
 
+        // find total bytes used under policy
         final long start = computeLastCycleBoundary(currentTime, policy);
         final long end = currentTime;
-
-        // find total bytes used under policy
         final long totalBytes = getTotalBytes(policy.template, start, end);
-        if (totalBytes == UNKNOWN_BYTES) return null;
 
         // report soft and hard limits under policy
         final long softLimitBytes = policy.warningBytes != WARNING_DISABLED ? policy.warningBytes
@@ -1369,6 +1353,7 @@
             try {
                 mScreenOn = mPowerManager.isScreenOn();
             } catch (RemoteException e) {
+                // ignored; service lives in system_server
             }
             updateRulesForScreenLocked();
         }
@@ -1448,7 +1433,7 @@
             // adjust stats accounting based on foreground status
             mNetworkStats.setUidForeground(uid, uidForeground);
         } catch (RemoteException e) {
-            Slog.w(TAG, "problem dispatching foreground change");
+            // ignored; service lives in system_server
         }
     }
 
@@ -1498,9 +1483,9 @@
         try {
             mNetworkManager.setInterfaceQuota(iface, quotaBytes);
         } catch (IllegalStateException e) {
-            Slog.e(TAG, "problem setting interface quota", e);
+            Log.wtf(TAG, "problem setting interface quota", e);
         } catch (RemoteException e) {
-            Slog.e(TAG, "problem setting interface quota", e);
+            // ignored; service lives in system_server
         }
     }
 
@@ -1508,29 +1493,9 @@
         try {
             mNetworkManager.removeInterfaceQuota(iface);
         } catch (IllegalStateException e) {
-            Slog.e(TAG, "problem removing interface quota", e);
+            Log.wtf(TAG, "problem removing interface quota", e);
         } catch (RemoteException e) {
-            Slog.e(TAG, "problem removing interface quota", e);
-        }
-    }
-
-    private void setInterfaceAlert(String iface, long alertBytes) {
-        try {
-            mNetworkManager.setInterfaceAlert(iface, alertBytes);
-        } catch (IllegalStateException e) {
-            Slog.e(TAG, "problem setting interface alert", e);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "problem setting interface alert", e);
-        }
-    }
-
-    private void removeInterfaceAlert(String iface) {
-        try {
-            mNetworkManager.removeInterfaceAlert(iface);
-        } catch (IllegalStateException e) {
-            Slog.e(TAG, "problem removing interface alert", e);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "problem removing interface alert", e);
+            // ignored; service lives in system_server
         }
     }
 
@@ -1538,9 +1503,9 @@
         try {
             mNetworkManager.setUidNetworkRules(uid, rejectOnQuotaInterfaces);
         } catch (IllegalStateException e) {
-            Slog.e(TAG, "problem setting uid rules", e);
+            Log.wtf(TAG, "problem setting uid rules", e);
         } catch (RemoteException e) {
-            Slog.e(TAG, "problem setting uid rules", e);
+            // ignored; service lives in system_server
         }
     }
 
@@ -1556,7 +1521,7 @@
             try {
                 mConnManager.setPolicyDataEnable(networkType, enabled);
             } catch (RemoteException e) {
-                Slog.e(TAG, "problem setting network enabled", e);
+                // ignored; service lives in system_server
             }
 
             mActiveNetworkEnabled.put(networkType, enabled);
@@ -1569,8 +1534,6 @@
         return telephony.getSubscriberId();
     }
 
-    private static final long UNKNOWN_BYTES = -1;
-
     private long getTotalBytes(NetworkTemplate template, long start, long end) {
         try {
             final NetworkStats stats = mNetworkStats.getSummaryForNetwork(
@@ -1578,8 +1541,8 @@
             final NetworkStats.Entry entry = stats.getValues(0, null);
             return entry.rxBytes + entry.txBytes;
         } catch (RemoteException e) {
-            Slog.w(TAG, "problem reading summary for template " + template);
-            return UNKNOWN_BYTES;
+            // ignored; service lives in system_server
+            return 0;
         }
     }
 
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 4d54fd4..af29d85 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -79,6 +79,7 @@
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
 import android.util.EventLog;
+import android.util.Log;
 import android.util.NtpTrustedTime;
 import android.util.Slog;
 import android.util.SparseIntArray;
@@ -128,7 +129,16 @@
     private static final int VERSION_UID_WITH_SET = 4;
 
     private static final int MSG_PERFORM_POLL = 0x1;
-    private static final int MSG_PERFORM_POLL_DETAILED = 0x2;
+
+    /** Flags to control detail level of poll event. */
+    private static final int FLAG_POLL_NETWORK = 0x1;
+    private static final int FLAG_POLL_UID = 0x2;
+    private static final int FLAG_PERSIST_NETWORK = 0x10;
+    private static final int FLAG_PERSIST_UID = 0x20;
+    private static final int FLAG_FORCE_PERSIST = 0x100;
+
+    private static final int FLAG_POLL_ALL = FLAG_POLL_NETWORK | FLAG_POLL_UID;
+    private static final int FLAG_PERSIST_ALL = FLAG_PERSIST_NETWORK | FLAG_PERSIST_UID;
 
     private final Context mContext;
     private final INetworkManagementService mNetworkManager;
@@ -261,9 +271,7 @@
         try {
             mNetworkManager.registerObserver(mAlertObserver);
         } catch (RemoteException e) {
-            // ouch, no push updates means we fall back to
-            // ACTION_NETWORK_STATS_POLL intervals.
-            Slog.e(TAG, "unable to register INetworkManagementEventObserver", e);
+            // ignored; service lives in system_server
         }
 
         registerPollAlarmLocked();
@@ -305,7 +313,7 @@
             mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, currentRealtime,
                     mSettings.getPollInterval(), mPollIntent);
         } catch (RemoteException e) {
-            Slog.w(TAG, "problem registering for poll alarm: " + e);
+            // ignored; service lives in system_server
         }
     }
 
@@ -321,7 +329,7 @@
         } catch (IllegalStateException e) {
             Slog.w(TAG, "problem registering for global alert: " + e);
         } catch (RemoteException e) {
-            Slog.w(TAG, "problem registering for global alert: " + e);
+            // ignored; service lives in system_server
         }
     }
 
@@ -509,7 +517,7 @@
     @Override
     public void forceUpdate() {
         mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
-        performPoll(true, false);
+        performPoll(FLAG_POLL_ALL | FLAG_PERSIST_ALL);
     }
 
     /**
@@ -538,7 +546,7 @@
         public void onReceive(Context context, Intent intent) {
             // on background handler thread, and verified UPDATE_DEVICE_STATS
             // permission above.
-            performPoll(true, false);
+            performPoll(FLAG_POLL_ALL | FLAG_PERSIST_ALL);
 
             // verify that we're watching global alert
             registerGlobalAlert();
@@ -585,7 +593,8 @@
             if (LIMIT_GLOBAL_ALERT.equals(limitName)) {
                 // kick off background poll to collect network stats; UID stats
                 // are handled during normal polling interval.
-                mHandler.obtainMessage(MSG_PERFORM_POLL).sendToTarget();
+                final int flags = FLAG_POLL_NETWORK | FLAG_PERSIST_NETWORK;
+                mHandler.obtainMessage(MSG_PERFORM_POLL, flags, 0).sendToTarget();
 
                 // re-arm global alert for next update
                 registerGlobalAlert();
@@ -605,13 +614,17 @@
         // take one last stats snapshot before updating iface mapping. this
         // isn't perfect, since the kernel may already be counting traffic from
         // the updated network.
-        performPollLocked(false, false);
+
+        // poll both network and UID stats, but only persist network stats,
+        // since this codepath should stay fast. UID stats will be persisted
+        // during next alarm poll event.
+        performPollLocked(FLAG_POLL_ALL | FLAG_PERSIST_NETWORK);
 
         final NetworkState[] states;
         try {
             states = mConnManager.getAllNetworkState();
         } catch (RemoteException e) {
-            Slog.w(TAG, "problem reading network state");
+            // ignored; service lives in system_server
             return;
         }
 
@@ -646,15 +659,15 @@
         } catch (IllegalStateException e) {
             Slog.w(TAG, "problem reading network stats: " + e);
         } catch (RemoteException e) {
-            Slog.w(TAG, "problem reading network stats: " + e);
+            // ignored; service lives in system_server
         }
     }
 
-    private void performPoll(boolean detailedPoll, boolean forcePersist) {
+    private void performPoll(int flags) {
         synchronized (mStatsLock) {
             mWakeLock.acquire();
             try {
-                performPollLocked(detailedPoll, forcePersist);
+                performPollLocked(flags);
             } finally {
                 mWakeLock.release();
             }
@@ -664,14 +677,17 @@
     /**
      * Periodic poll operation, reading current statistics and recording into
      * {@link NetworkStatsHistory}.
-     *
-     * @param detailedPoll Indicate if detailed UID stats should be collected
-     *            during this poll operation.
      */
-    private void performPollLocked(boolean detailedPoll, boolean forcePersist) {
-        if (LOGV) Slog.v(TAG, "performPollLocked()");
+    private void performPollLocked(int flags) {
+        if (LOGV) Slog.v(TAG, "performPollLocked(flags=0x" + Integer.toHexString(flags) + ")");
         final long startRealtime = SystemClock.elapsedRealtime();
 
+        final boolean pollNetwork = (flags & FLAG_POLL_NETWORK) != 0;
+        final boolean pollUid = (flags & FLAG_POLL_UID) != 0;
+        final boolean persistNetwork = (flags & FLAG_PERSIST_NETWORK) != 0;
+        final boolean persistUid = (flags & FLAG_PERSIST_UID) != 0;
+        final boolean forcePersist = (flags & FLAG_FORCE_PERSIST) != 0;
+
         // try refreshing time source when stale
         if (mTime.getCacheAge() > mSettings.getTimeCacheMaxAge()) {
             mTime.forceRefresh();
@@ -680,41 +696,40 @@
         // TODO: consider marking "untrusted" times in historical stats
         final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
                 : System.currentTimeMillis();
-        final long persistThreshold = mSettings.getPersistThreshold();
+        final long threshold = mSettings.getPersistThreshold();
 
-        final NetworkStats networkSnapshot;
-        final NetworkStats uidSnapshot;
         try {
-            networkSnapshot = mNetworkManager.getNetworkStatsSummary();
-            uidSnapshot = detailedPoll ? mNetworkManager.getNetworkStatsUidDetail(UID_ALL) : null;
-        } catch (IllegalStateException e) {
-            Slog.w(TAG, "problem reading network stats: " + e);
-            return;
-        } catch (RemoteException e) {
-            Slog.w(TAG, "problem reading network stats: " + e);
-            return;
-        }
+            if (pollNetwork) {
+                final NetworkStats networkSnapshot = mNetworkManager.getNetworkStatsSummary();
+                performNetworkPollLocked(networkSnapshot, currentTime);
 
-        performNetworkPollLocked(networkSnapshot, currentTime);
-
-        // persist when enough network data has occurred
-        final NetworkStats persistNetworkDelta = computeStatsDelta(
-                mLastPersistNetworkSnapshot, networkSnapshot, true);
-        if (forcePersist || persistNetworkDelta.getTotalBytes() > persistThreshold) {
-            writeNetworkStatsLocked();
-            mLastPersistNetworkSnapshot = networkSnapshot;
-        }
-
-        if (detailedPoll) {
-            performUidPollLocked(uidSnapshot, currentTime);
-
-            // persist when enough network data has occurred
-            final NetworkStats persistUidDelta = computeStatsDelta(
-                    mLastPersistUidSnapshot, uidSnapshot, true);
-            if (forcePersist || persistUidDelta.getTotalBytes() > persistThreshold) {
-                writeUidStatsLocked();
-                mLastPersistUidSnapshot = networkSnapshot;
+                // persist when enough network data has occurred
+                final NetworkStats persistNetworkDelta = computeStatsDelta(
+                        mLastPersistNetworkSnapshot, networkSnapshot, true);
+                final boolean pastThreshold = persistNetworkDelta.getTotalBytes() > threshold;
+                if (forcePersist || (persistNetwork && pastThreshold)) {
+                    writeNetworkStatsLocked();
+                    mLastPersistNetworkSnapshot = networkSnapshot;
+                }
             }
+
+            if (pollUid) {
+                final NetworkStats uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL);
+                performUidPollLocked(uidSnapshot, currentTime);
+
+                // persist when enough network data has occurred
+                final NetworkStats persistUidDelta = computeStatsDelta(
+                        mLastPersistUidSnapshot, uidSnapshot, true);
+                final boolean pastThreshold = persistUidDelta.getTotalBytes() > threshold;
+                if (forcePersist || (persistUid && pastThreshold)) {
+                    writeUidStatsLocked();
+                    mLastPersistUidSnapshot = uidSnapshot;
+                }
+            }
+        } catch (IllegalStateException e) {
+            Log.wtf(TAG, "problem reading network stats", e);
+        } catch (RemoteException e) {
+            // ignored; service lives in system_server
         }
 
         if (LOGV) {
@@ -722,8 +737,8 @@
             Slog.v(TAG, "performPollLocked() took " + duration + "ms");
         }
 
-        // sample stats after detailed poll
-        if (detailedPoll) {
+        // sample stats after each full poll
+        if (pollNetwork && pollUid) {
             performSample();
         }
 
@@ -785,6 +800,10 @@
             entry = delta.getValues(i, entry);
             final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface);
             if (ident == null) {
+                if (entry.rxBytes > 0 || entry.rxPackets > 0 || entry.txBytes > 0
+                        || entry.txPackets > 0) {
+                    Log.w(TAG, "dropping UID delta from unknown iface: " + entry);
+                }
                 continue;
             }
 
@@ -959,7 +978,7 @@
         } catch (FileNotFoundException e) {
             // missing stats is okay, probably first boot
         } catch (IOException e) {
-            Slog.e(TAG, "problem reading network stats", e);
+            Log.wtf(TAG, "problem reading network stats", e);
         } finally {
             IoUtils.closeQuietly(in);
         }
@@ -1032,7 +1051,7 @@
         } catch (FileNotFoundException e) {
             // missing stats is okay, probably first boot
         } catch (IOException e) {
-            Slog.e(TAG, "problem reading uid stats", e);
+            Log.wtf(TAG, "problem reading uid stats", e);
         } finally {
             IoUtils.closeQuietly(in);
         }
@@ -1061,7 +1080,7 @@
             out.flush();
             mNetworkFile.finishWrite(fos);
         } catch (IOException e) {
-            Slog.w(TAG, "problem writing stats: ", e);
+            Log.wtf(TAG, "problem writing stats", e);
             if (fos != null) {
                 mNetworkFile.failWrite(fos);
             }
@@ -1115,7 +1134,7 @@
             out.flush();
             mUidFile.finishWrite(fos);
         } catch (IOException e) {
-            Slog.w(TAG, "problem writing stats: ", e);
+            Log.wtf(TAG, "problem writing stats", e);
             if (fos != null) {
                 mUidFile.failWrite(fos);
             }
@@ -1142,7 +1161,7 @@
             }
 
             if (argSet.contains("poll")) {
-                performPollLocked(true, true);
+                performPollLocked(FLAG_POLL_ALL | FLAG_PERSIST_ALL | FLAG_FORCE_PERSIST);
                 pw.println("Forced poll");
                 return;
             }
@@ -1273,11 +1292,8 @@
         public boolean handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_PERFORM_POLL: {
-                    performPoll(false, false);
-                    return true;
-                }
-                case MSG_PERFORM_POLL_DETAILED: {
-                    performPoll(true, false);
+                    final int flags = msg.arg1;
+                    performPoll(flags);
                     return true;
                 }
                 default: {
@@ -1349,7 +1365,7 @@
             return getSecureLong(NETSTATS_POLL_INTERVAL, 30 * MINUTE_IN_MILLIS);
         }
         public long getPersistThreshold() {
-            return getSecureLong(NETSTATS_PERSIST_THRESHOLD, 512 * KB_IN_BYTES);
+            return getSecureLong(NETSTATS_PERSIST_THRESHOLD, 2 * MB_IN_BYTES);
         }
         public long getNetworkBucketDuration() {
             return getSecureLong(NETSTATS_NETWORK_BUCKET_DURATION, HOUR_IN_MILLIS);
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 54f3bb0..e7f1d9a 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -178,6 +178,7 @@
         expectDefaultSettings();
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
 
         replay();
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
@@ -230,6 +231,7 @@
         expectDefaultSettings();
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
 
         replay();
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
@@ -320,6 +322,7 @@
         expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
 
         replay();
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
@@ -370,6 +373,7 @@
         expectDefaultSettings();
         expectNetworkState(buildMobile3gState(IMSI_1));
         expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
 
         replay();
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
@@ -450,6 +454,7 @@
         expectDefaultSettings();
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
 
         replay();
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
@@ -507,6 +512,7 @@
         expectDefaultSettings();
         expectNetworkState(buildMobile3gState(IMSI_1));
         expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
 
         replay();
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
@@ -573,6 +579,7 @@
         expectDefaultSettings();
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
 
         replay();
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
@@ -635,6 +642,7 @@
         expectDefaultSettings();
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
 
         replay();
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
diff --git a/test-runner/src/android/test/InstrumentationCoreTestRunner.java b/test-runner/src/android/test/InstrumentationCoreTestRunner.java
index ff99a74..036a2275 100644
--- a/test-runner/src/android/test/InstrumentationCoreTestRunner.java
+++ b/test-runner/src/android/test/InstrumentationCoreTestRunner.java
@@ -51,35 +51,33 @@
 
     /**
      * Convenience definition of our log tag.
-     */ 
+     */
     private static final String TAG = "InstrumentationCoreTestRunner";
-    
+
     /**
      * True if (and only if) we are running in single-test mode (as opposed to
      * batch mode).
      */
     private boolean singleTest = false;
-    
+
     @Override
     public void onCreate(Bundle arguments) {
         // We might want to move this to /sdcard, if is is mounted/writable.
         File cacheDir = getTargetContext().getCacheDir();
 
-        // Set some properties that the core tests absolutely need. 
+        // Set some properties that the core tests absolutely need.
         System.setProperty("user.language", "en");
         System.setProperty("user.region", "US");
-        
+
         System.setProperty("java.home", cacheDir.getAbsolutePath());
         System.setProperty("user.home", cacheDir.getAbsolutePath());
         System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath());
-        System.setProperty("javax.net.ssl.trustStore",
-                "/etc/security/cacerts.bks");
-        
+
         if (arguments != null) {
             String classArg = arguments.getString(ARGUMENT_TEST_CLASS);
-            singleTest = classArg != null && classArg.contains("#"); 
+            singleTest = classArg != null && classArg.contains("#");
         }
-        
+
         super.onCreate(arguments);
     }
 
@@ -89,36 +87,36 @@
 
         runner.addTestListener(new TestListener() {
             /**
-             * The last test class we executed code from.  
+             * The last test class we executed code from.
              */
             private Class<?> lastClass;
-            
+
             /**
              * The minimum time we expect a test to take.
              */
             private static final int MINIMUM_TIME = 100;
-            
+
             /**
              * The start time of our current test in System.currentTimeMillis().
              */
             private long startTime;
-            
+
             public void startTest(Test test) {
                 if (test.getClass() != lastClass) {
                     lastClass = test.getClass();
                     printMemory(test.getClass());
                 }
-                
+
                 Thread.currentThread().setContextClassLoader(
                         test.getClass().getClassLoader());
-                
+
                 startTime = System.currentTimeMillis();
             }
-            
+
             public void endTest(Test test) {
                 if (test instanceof TestCase) {
                     cleanup((TestCase)test);
-                    
+
                     /*
                      * Make sure all tests take at least MINIMUM_TIME to
                      * complete. If they don't, we wait a bit. The Cupcake
@@ -126,7 +124,7 @@
                      * short time, which causes headache for the CTS.
                      */
                     long timeTaken = System.currentTimeMillis() - startTime;
-                    
+
                     if (timeTaken < MINIMUM_TIME) {
                         try {
                             Thread.sleep(MINIMUM_TIME - timeTaken);
@@ -136,15 +134,15 @@
                     }
                 }
             }
-            
+
             public void addError(Test test, Throwable t) {
                 // This space intentionally left blank.
             }
-            
+
             public void addFailure(Test test, AssertionFailedError t) {
                 // This space intentionally left blank.
             }
-            
+
             /**
              * Dumps some memory info.
              */
@@ -154,7 +152,7 @@
                 long total = runtime.totalMemory();
                 long free = runtime.freeMemory();
                 long used = total - free;
-                
+
                 Log.d(TAG, "Total memory  : " + total);
                 Log.d(TAG, "Used memory   : " + used);
                 Log.d(TAG, "Free memory   : " + free);
@@ -170,7 +168,7 @@
              */
             private void cleanup(TestCase test) {
                 Class<?> clazz = test.getClass();
-                
+
                 while (clazz != TestCase.class) {
                     Field[] fields = clazz.getDeclaredFields();
                     for (int i = 0; i < fields.length; i++) {
@@ -185,15 +183,15 @@
                             }
                         }
                     }
-                    
+
                     clazz = clazz.getSuperclass();
                 }
             }
-            
+
         });
-        
+
         return runner;
-    }    
+    }
 
     @Override
     List<Predicate<TestMethod>> getBuilderRequirements() {
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 0757efd..27a60cd 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -18,7 +18,7 @@
 
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WpsConfiguration;
+import android.net.wifi.Wps;
 import android.net.wifi.WpsResult;
 import android.net.wifi.ScanResult;
 import android.net.DhcpInfo;
diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java
index 83dda5c..c75dec7 100644
--- a/wifi/java/android/net/wifi/WifiConfigStore.java
+++ b/wifi/java/android/net/wifi/WifiConfigStore.java
@@ -397,7 +397,7 @@
      * Start WPS pin method configuration with pin obtained
      * from the access point
      */
-    static WpsResult startWpsWithPinFromAccessPoint(WpsConfiguration config) {
+    static WpsResult startWpsWithPinFromAccessPoint(Wps config) {
         WpsResult result = new WpsResult();
         if (WifiNative.startWpsWithPinFromAccessPointCommand(config.BSSID, config.pin)) {
             /* WPS leaves all networks disabled */
@@ -415,7 +415,7 @@
      * from the device
      * @return WpsResult indicating status and pin
      */
-    static WpsResult startWpsWithPinFromDevice(WpsConfiguration config) {
+    static WpsResult startWpsWithPinFromDevice(Wps config) {
         WpsResult result = new WpsResult();
         result.pin = WifiNative.startWpsWithPinFromDeviceCommand(config.BSSID);
         /* WPS leaves all networks disabled */
@@ -432,7 +432,7 @@
     /**
      * Start WPS push button configuration
      */
-    static WpsResult startWpsPbc(WpsConfiguration config) {
+    static WpsResult startWpsPbc(Wps config) {
         WpsResult result = new WpsResult();
         if (WifiNative.startWpsPbcCommand(config.BSSID)) {
             /* WPS leaves all networks disabled */
@@ -594,7 +594,7 @@
         sendConfiguredNetworksChangedBroadcast();
     }
 
-    static void updateIpAndProxyFromWpsConfig(int netId, WpsConfiguration wpsConfig) {
+    static void updateIpAndProxyFromWpsConfig(int netId, Wps wpsConfig) {
         synchronized (sConfiguredNetworks) {
             WifiConfiguration config = sConfiguredNetworks.get(netId);
             if (config != null) {
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 5f8385c..0fce8e8 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1175,7 +1175,7 @@
      * @param config WPS configuration
      * @hide
      */
-    public void startWps(WpsConfiguration config) {
+    public void startWps(Wps config) {
         if (config == null) {
             return;
         }
diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java
index cebcc47..6cc09e9 100644
--- a/wifi/java/android/net/wifi/WifiNative.java
+++ b/wifi/java/android/net/wifi/WifiNative.java
@@ -261,10 +261,10 @@
     public static String p2pConnect(WifiP2pConfig config, boolean joinExistingGroup) {
         if (config == null) return null;
         List<String> args = new ArrayList<String>();
-        WpsConfiguration wpsConfig = config.wpsConfig;
+        Wps wps = config.wps;
         args.add(config.deviceAddress);
 
-        switch (wpsConfig.setup) {
+        switch (wps.setup) {
             case PBC:
                 args.add("pbc");
                 break;
@@ -274,11 +274,11 @@
                 args.add("display");
                 break;
             case KEYPAD:
-                args.add(wpsConfig.pin);
+                args.add(wps.pin);
                 args.add("keypad");
                 break;
             case LABEL:
-                args.add(wpsConfig.pin);
+                args.add(wps.pin);
                 args.add("label");
             default:
                 break;
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index d116e5b..175a9ce 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -880,7 +880,7 @@
         sendMessage(message);
     }
 
-    public void startWps(Messenger replyTo, WpsConfiguration config) {
+    public void startWps(Messenger replyTo, Wps config) {
         Message msg = obtainMessage(CMD_START_WPS, config);
         msg.replyTo = replyTo;
         sendMessage(msg);
diff --git a/wifi/java/android/net/wifi/WpsConfiguration.aidl b/wifi/java/android/net/wifi/Wps.aidl
similarity index 95%
rename from wifi/java/android/net/wifi/WpsConfiguration.aidl
rename to wifi/java/android/net/wifi/Wps.aidl
index 6c26833..ba82a9a 100644
--- a/wifi/java/android/net/wifi/WpsConfiguration.aidl
+++ b/wifi/java/android/net/wifi/Wps.aidl
@@ -16,4 +16,4 @@
 
 package android.net.wifi;
 
-parcelable WpsConfiguration;
+parcelable Wps;
diff --git a/wifi/java/android/net/wifi/WpsConfiguration.java b/wifi/java/android/net/wifi/Wps.java
similarity index 85%
rename from wifi/java/android/net/wifi/WpsConfiguration.java
rename to wifi/java/android/net/wifi/Wps.java
index 0c2adfd..6d006960 100644
--- a/wifi/java/android/net/wifi/WpsConfiguration.java
+++ b/wifi/java/android/net/wifi/Wps.java
@@ -25,12 +25,14 @@
 import java.util.BitSet;
 
 /**
- * A class representing a WPS network configuration
+ * A class representing Wi-Fi Protected Setup
  * @hide
+ *
+ * {@see WifiP2pConfig}
  */
-public class WpsConfiguration implements Parcelable {
+public class Wps implements Parcelable {
 
-    /* Wi-Fi Protected Setup. www.wi-fi.org/wifi-protected-setup has details */
+    /** Wi-Fi Protected Setup. www.wi-fi.org/wifi-protected-setup has details */
     public enum Setup {
         /* Push button configuration */
         PBC,
@@ -49,6 +51,7 @@
     /** @hide */
     public String BSSID;
 
+    /** Passed with pin method configuration */
     public String pin;
 
     /** @hide */
@@ -60,8 +63,7 @@
     /** @hide */
     public LinkProperties linkProperties;
 
-    /** @hide */
-    public WpsConfiguration() {
+    public Wps() {
         setup = Setup.INVALID;
         BSSID = null;
         pin = null;
@@ -94,7 +96,7 @@
     }
 
     /** copy constructor {@hide} */
-    public WpsConfiguration(WpsConfiguration source) {
+    public Wps(Wps source) {
         if (source != null) {
             setup = source.setup;
             BSSID = source.BSSID;
@@ -116,10 +118,10 @@
     }
 
     /** Implement the Parcelable interface {@hide} */
-    public static final Creator<WpsConfiguration> CREATOR =
-        new Creator<WpsConfiguration>() {
-            public WpsConfiguration createFromParcel(Parcel in) {
-                WpsConfiguration config = new WpsConfiguration();
+    public static final Creator<Wps> CREATOR =
+        new Creator<Wps>() {
+            public Wps createFromParcel(Parcel in) {
+                Wps config = new Wps();
                 config.setup = Setup.valueOf(in.readString());
                 config.BSSID = in.readString();
                 config.pin = in.readString();
@@ -129,8 +131,8 @@
                 return config;
             }
 
-            public WpsConfiguration[] newArray(int size) {
-                return new WpsConfiguration[size];
+            public Wps[] newArray(int size) {
+                return new Wps[size];
             }
         };
 }
diff --git a/wifi/java/android/net/wifi/WpsStateMachine.java b/wifi/java/android/net/wifi/WpsStateMachine.java
index af089ab..f9e903a 100644
--- a/wifi/java/android/net/wifi/WpsStateMachine.java
+++ b/wifi/java/android/net/wifi/WpsStateMachine.java
@@ -53,7 +53,7 @@
 
     private WifiStateMachine mWifiStateMachine;
 
-    private WpsConfiguration mWpsConfig;
+    private Wps mWpsConfig;
 
     private Context mContext;
     AsyncChannel mReplyChannel = new AsyncChannel();
@@ -90,10 +90,10 @@
         @Override
         public boolean processMessage(Message message) {
             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
-            WpsConfiguration wpsConfig;
+            Wps wpsConfig;
             switch (message.what) {
                 case WifiStateMachine.CMD_START_WPS:
-                    mWpsConfig = (WpsConfiguration) message.obj;
+                    mWpsConfig = (Wps) message.obj;
                     WpsResult result;
                     switch (mWpsConfig.setup) {
                         case PBC:
diff --git a/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl b/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl
index a0c7dd1..381a450 100644
--- a/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl
+++ b/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl
@@ -26,6 +26,5 @@
 interface IWifiP2pManager
 {
     Messenger getMessenger();
-    boolean isP2pSupported();
 }
 
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
index 686d698..e359ce5 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
@@ -16,26 +16,28 @@
 
 package android.net.wifi.p2p;
 
-import android.net.wifi.WpsConfiguration;
-import android.net.wifi.WpsConfiguration.Setup;
+import android.net.wifi.Wps;
+import android.net.wifi.Wps.Setup;
 import android.os.Parcelable;
 import android.os.Parcel;
 
 /**
- * A class representing a Wi-Fi P2p configuration
+ * A class representing a Wi-Fi P2p configuration for setting up a connection
  * @hide
+ *
+ * {@see WifiP2pManager}
  */
 public class WifiP2pConfig implements Parcelable {
 
     /**
-     * Device address
+     * The device MAC address uniquely identifies a Wi-Fi p2p device
      */
     public String deviceAddress;
 
     /**
-     * WPS configuration
+     * Wi-Fi Protected Setup information
      */
-    public WpsConfiguration wpsConfig;
+    public Wps wps;
 
     /**
      * This is an integer value between 0 and 15 where 0 indicates the least
@@ -61,11 +63,11 @@
 
     public WifiP2pConfig() {
         //set defaults
-        wpsConfig = new WpsConfiguration();
-        wpsConfig.setup = Setup.PBC;
+        wps = new Wps();
+        wps.setup = Setup.PBC;
     }
 
-    /* P2P-GO-NEG-REQUEST 42:fc:89:a8:96:09 dev_passwd_id=4 */
+    /** P2P-GO-NEG-REQUEST 42:fc:89:a8:96:09 dev_passwd_id=4 {@hide}*/
     public WifiP2pConfig(String supplicantEvent) throws IllegalArgumentException {
         String[] tokens = supplicantEvent.split(" ");
 
@@ -74,7 +76,7 @@
         }
 
         deviceAddress = tokens[1];
-        wpsConfig = new WpsConfiguration();
+        wps = new Wps();
 
         if (tokens.length > 2) {
             String[] nameVal = tokens[2].split("=");
@@ -87,28 +89,29 @@
             //As defined in wps/wps_defs.h
             switch (devPasswdId) {
                 case 0x00:
-                    wpsConfig.setup = Setup.LABEL;
+                    wps.setup = Setup.LABEL;
                     break;
                 case 0x01:
-                    wpsConfig.setup = Setup.KEYPAD;
+                    wps.setup = Setup.KEYPAD;
                     break;
                 case 0x04:
-                    wpsConfig.setup = Setup.PBC;
+                    wps.setup = Setup.PBC;
                     break;
                 case 0x05:
-                    wpsConfig.setup = Setup.DISPLAY;
+                    wps.setup = Setup.DISPLAY;
                     break;
                 default:
-                    wpsConfig.setup = Setup.PBC;
+                    wps.setup = Setup.PBC;
                     break;
             }
         }
     }
 
+    /** @hide */
     public String toString() {
         StringBuffer sbuf = new StringBuffer();
         sbuf.append("\n address: ").append(deviceAddress);
-        sbuf.append("\n wps: ").append(wpsConfig);
+        sbuf.append("\n wps: ").append(wps);
         sbuf.append("\n groupOwnerIntent: ").append(groupOwnerIntent);
         sbuf.append("\n persist: ").append(persist.toString());
         return sbuf.toString();
@@ -129,7 +132,7 @@
     /** Implement the Parcelable interface {@hide} */
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(deviceAddress);
-        dest.writeParcelable(wpsConfig, flags);
+        dest.writeParcelable(wps, flags);
         dest.writeInt(groupOwnerIntent);
         dest.writeString(persist.name());
     }
@@ -140,7 +143,7 @@
             public WifiP2pConfig createFromParcel(Parcel in) {
                 WifiP2pConfig config = new WifiP2pConfig();
                 config.deviceAddress = in.readString();
-                config.wpsConfig = (WpsConfiguration) in.readParcelable(null);
+                config.wps = (Wps) in.readParcelable(null);
                 config.groupOwnerIntent = in.readInt();
                 config.persist = Persist.valueOf(in.readString());
                 return config;
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
index 7908726..99c585f 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
@@ -25,17 +25,20 @@
 /**
  * A class representing a Wi-Fi p2p device
  * @hide
+ *
+ * {@see WifiP2pManager}
  */
 public class WifiP2pDevice implements Parcelable {
 
     private static final String TAG = "WifiP2pDevice";
+
     /**
-     * Device name
+     * The device name is a user friendly string to identify a Wi-Fi p2p device
      */
     public String deviceName;
 
     /**
-     * Device MAC address
+     * The device MAC address uniquely identifies a Wi-Fi p2p device
      */
     public String deviceAddress;
 
@@ -46,35 +49,30 @@
      * P2P Interface Address and the group interface will be created with
      * address as the local address in case of successfully completed
      * negotiation.
+     * @hide
      */
     public String interfaceAddress;
 
     /**
-     * Primary device type
+     * Primary device type identifies the type of device. For example, an application
+     * could filter the devices discovered to only display printers if the purpose is to
+     * enable a printing action from the user. See the Wi-Fi Direct technical specification
+     * for the full list of standard device types supported.
      */
     public String primaryDeviceType;
 
     /**
-     * Secondary device type
+     * Secondary device type is an optional attribute that can be provided by a device in
+     * addition to the primary device type.
      */
     public String secondaryDeviceType;
 
 
     // These definitions match the ones in wpa_supplicant
     /* WPS config methods supported */
-    private static final int WPS_CONFIG_USBA            = 0x0001;
-    private static final int WPS_CONFIG_ETHERNET        = 0x0002;
-    private static final int WPS_CONFIG_LABEL           = 0x0004;
     private static final int WPS_CONFIG_DISPLAY         = 0x0008;
-    private static final int WPS_CONFIG_EXT_NFC_TOKEN   = 0x0010;
-    private static final int WPS_CONFIG_INT_NFC_TOKEN   = 0x0020;
-    private static final int WPS_CONFIG_NFC_INTERFACE   = 0x0040;
     private static final int WPS_CONFIG_PUSHBUTTON      = 0x0080;
     private static final int WPS_CONFIG_KEYPAD          = 0x0100;
-    private static final int WPS_CONFIG_VIRT_PUSHBUTTON = 0x0280;
-    private static final int WPS_CONFIG_PHY_PUSHBUTTON  = 0x0480;
-    private static final int WPS_CONFIG_VIRT_DISPLAY    = 0x2008;
-    private static final int WPS_CONFIG_PHY_DISPLAY     = 0x4008;
 
     /* Device Capability bitmap */
     private static final int DEVICE_CAPAB_SERVICE_DISCOVERY         = 1;
@@ -95,19 +93,23 @@
 
     /**
      * WPS config methods supported
+     * @hide
      */
     public int wpsConfigMethodsSupported;
 
     /**
      * Device capability
+     * @hide
      */
     public int deviceCapability;
 
     /**
      * Group capability
+     * @hide
      */
     public int groupCapability;
 
+    /** Device connection status */
     public enum Status {
         CONNECTED,
         INVITED,
@@ -118,7 +120,7 @@
 
     public Status status = Status.UNAVAILABLE;
 
-    public WifiP2pDevice() {
+    WifiP2pDevice() {
     }
 
     /**
@@ -144,6 +146,7 @@
      *  group_capab=0x0
      *
      *  Note: The events formats can be looked up in the wpa_supplicant code
+     * @hide
      */
     public WifiP2pDevice(String string) throws IllegalArgumentException {
         String[] tokens = string.split(" ");
@@ -198,11 +201,33 @@
         }
     }
 
+    /** Returns true if WPS push button configuration is supported */
+    public boolean wpsPbcSupported() {
+        return (wpsConfigMethodsSupported & WPS_CONFIG_PUSHBUTTON) != 0;
+    }
+
+    /** Returns true if WPS keypad configuration is supported */
+    public boolean wpsKeypadSupported() {
+        return (wpsConfigMethodsSupported & WPS_CONFIG_KEYPAD) != 0;
+    }
+
+    /** Returns true if WPS display configuration is supported */
+    public boolean wpsDisplaySupported() {
+        return (wpsConfigMethodsSupported & WPS_CONFIG_DISPLAY) != 0;
+    }
+
+    /** Returns true if the device is capable of service discovery */
+    public boolean isServiceDiscoveryCapable() {
+        return (deviceCapability & DEVICE_CAPAB_SERVICE_DISCOVERY) != 0;
+    }
+
+    /** Returns true if the device is a group owner */
     public boolean isGroupOwner() {
         return (groupCapability & GROUP_CAPAB_GROUP_OWNER) != 0;
     }
 
     @Override
+    /** @hide */
     public boolean equals(Object obj) {
         if (this == obj) return true;
         if (!(obj instanceof WifiP2pDevice)) return false;
@@ -214,6 +239,7 @@
         return other.deviceAddress.equals(deviceAddress);
     }
 
+    /** @hide */
     public String toString() {
         StringBuffer sbuf = new StringBuffer();
         sbuf.append("Device: ").append(deviceName);
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java b/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
index aa3554c..242bce0 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
@@ -28,22 +28,25 @@
 /**
  * A class representing a Wi-Fi P2p device list
  * @hide
+ *
+ * {@see WifiP2pManager}
  */
 public class WifiP2pDeviceList implements Parcelable {
 
     private Collection<WifiP2pDevice> mDevices;
 
-    public WifiP2pDeviceList() {
+    WifiP2pDeviceList() {
         mDevices = new ArrayList<WifiP2pDevice>();
     }
 
-    //copy constructor
+    /** copy constructor {@hide} */
     public WifiP2pDeviceList(WifiP2pDeviceList source) {
         if (source != null) {
             mDevices = source.getDeviceList();
         }
     }
 
+    /** @hide */
     public WifiP2pDeviceList(ArrayList<WifiP2pDevice> devices) {
         mDevices = new ArrayList<WifiP2pDevice>();
         for (WifiP2pDevice device : devices) {
@@ -51,12 +54,14 @@
         }
     }
 
+    /** @hide */
     public boolean clear() {
         if (mDevices.isEmpty()) return false;
         mDevices.clear();
         return true;
     }
 
+    /** @hide */
     public void update(WifiP2pDevice device) {
         if (device == null) return;
         for (WifiP2pDevice d : mDevices) {
@@ -75,15 +80,18 @@
         mDevices.add(device);
     }
 
+    /** @hide */
     public boolean remove(WifiP2pDevice device) {
         if (device == null) return false;
         return mDevices.remove(device);
     }
 
+    /** Get the list of devices */
     public Collection<WifiP2pDevice> getDeviceList() {
         return Collections.unmodifiableCollection(mDevices);
     }
 
+    /** @hide */
     public String toString() {
         StringBuffer sbuf = new StringBuffer();
         for (WifiP2pDevice device : mDevices) {
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
index e35d360..48f210b 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
@@ -27,6 +27,8 @@
 /**
  * A class representing a Wi-Fi P2p group
  * @hide
+ *
+ * {@see WifiP2pManager}
  */
 public class WifiP2pGroup implements Parcelable {
 
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pInfo.java b/wifi/java/android/net/wifi/p2p/WifiP2pInfo.java
index a02175e..81b7708 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pInfo.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pInfo.java
@@ -23,15 +23,20 @@
 import java.net.UnknownHostException;
 
 /**
- * A class representing connection info on Wi-fi P2p
+ * A class representing connection information about a Wi-Fi p2p group
  * @hide
+ *
+ * {@see WifiP2pManager}
  */
 public class WifiP2pInfo implements Parcelable {
 
+    /** Indicates if a p2p group has been successfully formed */
     public boolean groupFormed;
 
+    /** Indicates if the current device is the group owner */
     public boolean isGroupOwner;
 
+    /** Group owner address */
     public InetAddress groupOwnerAddress;
 
     /** @hide */
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index 11de9c4..5715186 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -64,7 +64,7 @@
  * use {@link #requestConnectionInfo} to fetch the connection details. Connection information
  * can be obtained with {@link #connectionInfoInResponse} on a {@link #RESPONSE_CONNECTION_INFO}
  * message. The connection info {@link WifiP2pInfo} contains the address of the group owner
- * {@link WifiP2pInfo#groupOwnerAddress} and a flag {@link #WifiP2pInfo#isGroupOwner} to indicate
+ * {@link WifiP2pInfo#groupOwnerAddress} and a flag {@link WifiP2pInfo#isGroupOwner} to indicate
  * if the current device is a p2p group owner. A p2p client can thus communicate with
  * the p2p group owner through a socket connection.
  *
@@ -85,6 +85,7 @@
  * {@see WifiP2pGroup}
  * {@see WifiP2pDevice}
  * {@see WifiP2pDeviceList}
+ * {@see android.net.wifi.Wps}
  * @hide
  */
 public class WifiP2pManager {
@@ -399,15 +400,6 @@
         }
     }
 
-    /** @hide */
-    public boolean isP2pSupported() {
-        try {
-            return mService.isP2pSupported();
-        } catch (RemoteException e) {
-            return false;
-        }
-    }
-
     /**
      * Sends in a request to the system to enable p2p. This will pop up a dialog
      * to the user and upon authorization will enable p2p.
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pService.java b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
index 5297302..e2b2249 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pService.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
@@ -41,8 +41,8 @@
 import android.net.wifi.WifiMonitor;
 import android.net.wifi.WifiNative;
 import android.net.wifi.WifiStateMachine;
-import android.net.wifi.WpsConfiguration;
-import android.net.wifi.WpsConfiguration.Setup;
+import android.net.wifi.Wps;
+import android.net.wifi.Wps.Setup;
 import android.net.wifi.p2p.WifiP2pDevice.Status;
 import android.os.Binder;
 import android.os.IBinder;
@@ -128,7 +128,9 @@
     public static final int GROUP_NEGOTIATION_TIMED_OUT     =   BASE + 3;
 
     /* User accepted to disable Wi-Fi in order to enable p2p */
-    private static final int WIFI_DISABLE_USER_ACCEPT       =   BASE + 11;
+    private static final int WIFI_DISABLE_USER_ACCEPT       =   BASE + 4;
+    /* User rejected to disable Wi-Fi in order to enable p2p */
+    private static final int WIFI_DISABLE_USER_REJECT       =   BASE + 5;
 
     private final boolean mP2pSupported;
     private final String mDeviceType;
@@ -153,8 +155,9 @@
         mInterface = SystemProperties.get("wifi.interface", "wlan0");
         mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI_P2P, 0, NETWORKTYPE, "");
 
-        mP2pSupported = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_wifi_p2p_support);
+        mP2pSupported = mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_WIFI_DIRECT);
+
         mDeviceType = mContext.getResources().getString(
                 com.android.internal.R.string.config_wifi_p2p_device_type);
         mDeviceName = getDefaultDeviceName();
@@ -216,14 +219,6 @@
         return new Messenger(mP2pStateMachine.getHandler());
     }
 
-    /**
-     * Return if p2p is supported
-     */
-    public boolean isP2pSupported() {
-        enforceAccessPermission();
-        return mP2pSupported;
-    }
-
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -359,6 +354,7 @@
                     break;
                 // Ignore
                 case WIFI_DISABLE_USER_ACCEPT:
+                case WIFI_DISABLE_USER_REJECT:
                 case GROUP_NEGOTIATION_TIMED_OUT:
                     break;
                 default:
@@ -457,8 +453,7 @@
                             if (which == DialogInterface.BUTTON_POSITIVE) {
                                 sendMessage(WIFI_DISABLE_USER_ACCEPT);
                             } else {
-                                logd("User rejected enabling p2p");
-                                //ignore
+                                sendMessage(WIFI_DISABLE_USER_REJECT);
                             }
                         }
                     };
@@ -509,6 +504,11 @@
                     mWifiChannel.sendMessage(P2P_ENABLE_PENDING);
                     transitionTo(mWaitForWifiDisableState);
                     break;
+                case WIFI_DISABLE_USER_REJECT:
+                    logd("User rejected enabling p2p");
+                    sendP2pStateChangedBroadcast(false);
+                    transitionTo(mP2pDisabledState);
+                    break;
                 case WifiP2pManager.ENABLE_P2P:
                 case WifiP2pManager.DISABLE_P2P:
                     deferMessage(message);
@@ -1027,7 +1027,7 @@
 
     private void notifyP2pGoNegotationRequest(WifiP2pConfig config) {
         Resources r = Resources.getSystem();
-        WpsConfiguration wpsConfig = config.wpsConfig;
+        Wps wps = config.wps;
         final View textEntryView = LayoutInflater.from(mContext)
                 .inflate(R.layout.wifi_p2p_go_negotiation_request_alert, null);
         final EditText pin = (EditText) textEntryView .findViewById(R.id.wifi_p2p_wps_pin);
@@ -1040,10 +1040,10 @@
                             if (DBG) logd(getName() + " connect " + pin.getText());
 
                             if (pin.getVisibility() == View.GONE) {
-                                mSavedGoNegotiationConfig.wpsConfig.setup = Setup.PBC;
+                                mSavedGoNegotiationConfig.wps.setup = Setup.PBC;
                             } else {
-                                mSavedGoNegotiationConfig.wpsConfig.setup = Setup.KEYPAD;
-                                mSavedGoNegotiationConfig.wpsConfig.pin = pin.getText().toString();
+                                mSavedGoNegotiationConfig.wps.setup = Setup.KEYPAD;
+                                mSavedGoNegotiationConfig.wps.pin = pin.getText().toString();
                             }
                             sendMessage(WifiP2pManager.CONNECT, mSavedGoNegotiationConfig);
                             mSavedGoNegotiationConfig = null;
@@ -1058,7 +1058,7 @@
                     })
             .create();
 
-        if (wpsConfig.setup == Setup.PBC) {
+        if (wps.setup == Setup.PBC) {
             pin.setVisibility(View.GONE);
             dialog.setMessage(r.getString(R.string.wifi_p2p_pbc_go_negotiation_request_message,
                         config.deviceAddress));
@@ -1211,6 +1211,11 @@
         mReplyChannel.replyToMessage(msg, what);
     }
 
+    private void replyToMessage(Message msg, int what, int arg1) {
+        if (msg.replyTo == null) return;
+        mReplyChannel.replyToMessage(msg, what, arg1);
+    }
+
     private void replyToMessage(Message msg, int what, Object obj) {
         if (msg.replyTo == null) return;
         mReplyChannel.replyToMessage(msg, what, obj);