Merge "Clip windows to their background by default" into lmp-dev
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index 90e80d3..4dd7e07 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -195,6 +195,11 @@
             LEFT_OF, RIGHT_OF, ALIGN_LEFT, ALIGN_RIGHT, START_OF, END_OF, ALIGN_START, ALIGN_END
     };
 
+    /**
+     * Used to indicate left/right/top/bottom should be inferred from constraints
+     */
+    private static final int VALUE_NOT_SET = Integer.MIN_VALUE;
+
     private View mBaselineView = null;
     private boolean mHasBaselineAlignedChild;
 
@@ -670,8 +675,8 @@
 
     /**
      * Measure a child. The child should have left, top, right and bottom information
-     * stored in its LayoutParams. If any of these values is -1 it means that the view
-     * can extend up to the corresponding edge.
+     * stored in its LayoutParams. If any of these values is VALUE_NOT_SET it means
+     * that the view can extend up to the corresponding edge.
      *
      * @param child Child to measure
      * @param params LayoutParams associated with child
@@ -744,11 +749,11 @@
         int childSpecMode = 0;
         int childSpecSize = 0;
 
-        // Negative values in a mySize/myWidth/myWidth value in RelativeLayout
+        // Negative values in a mySize value in RelativeLayout
         // measurement is code for, "we got an unspecified mode in the
         // RelativeLayout's measure spec."
         if (mySize < 0 && !mAllowBrokenMeasureSpecs) {
-            if (childStart >= 0 && childEnd >= 0) {
+            if (childStart != VALUE_NOT_SET && childEnd != VALUE_NOT_SET) {
                 // Constraints fixed both edges, so child has an exact size.
                 childSpecSize = Math.max(0, childEnd - childStart);
                 childSpecMode = MeasureSpec.EXACTLY;
@@ -771,17 +776,17 @@
 
         // If the view did not express a layout constraint for an edge, use
         // view's margins and our padding
-        if (tempStart < 0) {
+        if (tempStart == VALUE_NOT_SET) {
             tempStart = startPadding + startMargin;
         }
-        if (tempEnd < 0) {
+        if (tempEnd == VALUE_NOT_SET) {
             tempEnd = mySize - endPadding - endMargin;
         }
 
         // Figure out maximum size available to this view
         int maxAvailable = tempEnd - tempStart;
 
-        if (childStart >= 0 && childEnd >= 0) {
+        if (childStart != VALUE_NOT_SET && childEnd != VALUE_NOT_SET) {
             // Constraints fixed both edges, so child must be an exact size
             childSpecMode = MeasureSpec.EXACTLY;
             childSpecSize = maxAvailable;
@@ -828,13 +833,13 @@
         final int layoutDirection = getLayoutDirection();
         int[] rules = params.getRules(layoutDirection);
 
-        if (params.mLeft < 0 && params.mRight >= 0) {
+        if (params.mLeft == VALUE_NOT_SET && params.mRight != VALUE_NOT_SET) {
             // Right is fixed, but left varies
             params.mLeft = params.mRight - child.getMeasuredWidth();
-        } else if (params.mLeft >= 0 && params.mRight < 0) {
+        } else if (params.mLeft != VALUE_NOT_SET && params.mRight == VALUE_NOT_SET) {
             // Left is fixed, but right varies
             params.mRight = params.mLeft + child.getMeasuredWidth();
-        } else if (params.mLeft < 0 && params.mRight < 0) {
+        } else if (params.mLeft == VALUE_NOT_SET && params.mRight == VALUE_NOT_SET) {
             // Both left and right vary
             if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
                 if (!wrapContent) {
@@ -864,13 +869,13 @@
 
         int[] rules = params.getRules();
 
-        if (params.mTop < 0 && params.mBottom >= 0) {
+        if (params.mTop == VALUE_NOT_SET && params.mBottom != VALUE_NOT_SET) {
             // Bottom is fixed, but top varies
             params.mTop = params.mBottom - child.getMeasuredHeight();
-        } else if (params.mTop >= 0 && params.mBottom < 0) {
+        } else if (params.mTop != VALUE_NOT_SET && params.mBottom == VALUE_NOT_SET) {
             // Top is fixed, but bottom varies
             params.mBottom = params.mTop + child.getMeasuredHeight();
-        } else if (params.mTop < 0 && params.mBottom < 0) {
+        } else if (params.mTop == VALUE_NOT_SET && params.mBottom == VALUE_NOT_SET) {
             // Both top and bottom vary
             if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {
                 if (!wrapContent) {
@@ -891,12 +896,14 @@
     private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth, int[] rules) {
         RelativeLayout.LayoutParams anchorParams;
 
-        // -1 indicated a "soft requirement" in that direction. For example:
-        // left=10, right=-1 means the view must start at 10, but can go as far as it wants to the right
-        // left =-1, right=10 means the view must end at 10, but can go as far as it wants to the left
+        // VALUE_NOT_SET indicates a "soft requirement" in that direction. For example:
+        // left=10, right=VALUE_NOT_SET means the view must start at 10, but can go as far as it
+        // wants to the right
+        // left=VALUE_NOT_SET, right=10 means the view must end at 10, but can go as far as it
+        // wants to the left
         // left=10, right=20 means the left and right ends are both fixed
-        childParams.mLeft = -1;
-        childParams.mRight = -1;
+        childParams.mLeft = VALUE_NOT_SET;
+        childParams.mRight = VALUE_NOT_SET;
 
         anchorParams = getRelatedViewParams(rules, LEFT_OF);
         if (anchorParams != null) {
@@ -947,8 +954,8 @@
         int[] rules = childParams.getRules();
         RelativeLayout.LayoutParams anchorParams;
 
-        childParams.mTop = -1;
-        childParams.mBottom = -1;
+        childParams.mTop = VALUE_NOT_SET;
+        childParams.mBottom = VALUE_NOT_SET;
 
         anchorParams = getRelatedViewParams(rules, ABOVE);
         if (anchorParams != null) {
@@ -1222,9 +1229,6 @@
 
         private int mLeft, mTop, mRight, mBottom;
 
-        private int mStart = DEFAULT_MARGIN_RELATIVE;
-        private int mEnd = DEFAULT_MARGIN_RELATIVE;
-
         private boolean mRulesChanged = false;
         private boolean mIsRtlCompatibilityMode = false;
 
@@ -1601,14 +1605,6 @@
         @Override
         public void resolveLayoutDirection(int layoutDirection) {
             final boolean isLayoutRtl = isLayoutRtl();
-            if (isLayoutRtl) {
-                if (mStart != DEFAULT_MARGIN_RELATIVE) mRight = mStart;
-                if (mEnd != DEFAULT_MARGIN_RELATIVE) mLeft = mEnd;
-            } else {
-                if (mStart != DEFAULT_MARGIN_RELATIVE) mLeft = mStart;
-                if (mEnd != DEFAULT_MARGIN_RELATIVE) mRight = mEnd;
-            }
-
             if (hasRelativeRules() && layoutDirection != getLayoutDirection()) {
                 resolveRules(layoutDirection);
             }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index fac0eb2..30831cd 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -222,6 +222,7 @@
  * @attr ref android.R.styleable#TextView_imeActionId
  * @attr ref android.R.styleable#TextView_editorExtras
  * @attr ref android.R.styleable#TextView_elegantTextHeight
+ * @attr ref android.R.styleable#TextView_letterSpacing
  */
 @RemoteView
 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
@@ -657,6 +658,7 @@
         int shadowcolor = 0;
         float dx = 0, dy = 0, r = 0;
         boolean elegant = false;
+        float letterSpacing = 0;
 
         final Resources.Theme theme = context.getTheme();
 
@@ -737,6 +739,10 @@
                 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
                     elegant = appearance.getBoolean(attr, false);
                     break;
+
+                case com.android.internal.R.styleable.TextAppearance_letterSpacing:
+                    letterSpacing = appearance.getFloat(attr, 0);
+                    break;
                 }
             }
 
@@ -1078,6 +1084,10 @@
             case com.android.internal.R.styleable.TextView_elegantTextHeight:
                 elegant = a.getBoolean(attr, false);
                 break;
+
+            case com.android.internal.R.styleable.TextView_letterSpacing:
+                letterSpacing = a.getFloat(attr, 0);
+                break;
             }
         }
         a.recycle();
@@ -1259,6 +1269,7 @@
         }
         setRawTextSize(textSize);
         setElegantTextHeight(elegant);
+        setLetterSpacing(letterSpacing);
 
         if (allCaps) {
             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
@@ -2487,6 +2498,11 @@
                 com.android.internal.R.styleable.TextAppearance_elegantTextHeight, false));
         }
 
+        if (appearance.hasValue(com.android.internal.R.styleable.TextAppearance_letterSpacing)) {
+            setLetterSpacing(appearance.getFloat(
+                com.android.internal.R.styleable.TextAppearance_letterSpacing, 0));
+        }
+
         appearance.recycle();
     }
 
@@ -2667,6 +2683,41 @@
     }
 
     /**
+     * @return the extent by which text is currently being letter-spaced.
+     * This will normally be 0.
+     *
+     * @see #setLetterSpacing(float)
+     * @hide
+     */
+    public float getLetterSpacing() {
+        return mTextPaint.getLetterSpacing();
+    }
+
+    /**
+     * Sets text letter-spacing.  The value is in 'EM' units.  Typical values
+     * for slight expansion will be around 0.05.  Negative values tighten text.
+     *
+     * @see #getLetterSpacing()
+     * @see Paint#setFlags
+     *
+     * @attr ref android.R.styleable#TextView_letterSpacing
+     * @hide
+     */
+    @android.view.RemotableViewMethod
+    public void setLetterSpacing(float letterSpacing) {
+        if (letterSpacing != mTextPaint.getLetterSpacing()) {
+            mTextPaint.setLetterSpacing(letterSpacing);
+
+            if (mLayout != null) {
+                nullLayouts();
+                requestLayout();
+                invalidate();
+            }
+        }
+    }
+
+
+    /**
      * Sets the text color for all the states (normal, selected,
      * focused) to be this color.
      *
diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp
index c66437a..802f2abe 100644
--- a/core/jni/android/graphics/MinikinUtils.cpp
+++ b/core/jni/android/graphics/MinikinUtils.cpp
@@ -29,6 +29,8 @@
 namespace android {
 
 // Do an sprintf starting at offset n, abort on overflow
+static int snprintfcat(char* buf, int off, int size, const char* format, ...)
+        __attribute__((__format__(__printf__, 4, 5)));
 static int snprintfcat(char* buf, int off, int size, const char* format, ...) {
     va_list args;
     va_start(args, format);
@@ -43,17 +45,18 @@
     TypefaceImpl* resolvedFace = TypefaceImpl_resolveDefault(typeface);
     layout->setFontCollection(resolvedFace->fFontCollection);
     FontStyle style = resolvedFace->fStyle;
-    char css[256];
+    char css[512];
     int off = snprintfcat(css, 0, sizeof(css),
         "font-size: %d; font-scale-x: %f; font-skew-x: %f; -paint-flags: %d;"
-        " font-weight: %d; font-style: %s; -minikin-bidi: %d;",
+        " font-weight: %d; font-style: %s; -minikin-bidi: %d; letter-spacing: %f;",
         (int)paint->getTextSize(),
         paint->getTextScaleX(),
         paint->getTextSkewX(),
         MinikinFontSkia::packPaintFlags(paint),
         style.getWeight() * 100,
         style.getItalic() ? "italic" : "normal",
-        bidiFlags);
+        bidiFlags,
+        paint->getLetterSpacing());
     SkString langString = paint->getPaintOptionsAndroid().getLanguage().getTag();
     off = snprintfcat(css, off, sizeof(css), " lang: %s;", langString.c_str());
     SkPaintOptionsAndroid::FontVariant var = paint->getPaintOptionsAndroid().getFontVariant();
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index c06f0d2..e2b3684 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -423,6 +423,16 @@
         GraphicsJNI::getNativePaint(env, paint)->setTextSkewX(skewX);
     }
 
+    static jfloat getLetterSpacing(JNIEnv* env, jobject clazz, jlong paintHandle) {
+        Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+        return paint->getLetterSpacing();
+    }
+
+    static void setLetterSpacing(JNIEnv* env, jobject clazz, jlong paintHandle, jfloat letterSpacing) {
+        Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+        paint->setLetterSpacing(letterSpacing);
+    }
+
     static SkScalar getMetricsInternal(JNIEnv* env, jobject jpaint, Paint::FontMetrics *metrics) {
         const int kElegantTop = 2500;
         const int kElegantBottom = -1000;
@@ -988,6 +998,8 @@
     {"setTextScaleX","(F)V", (void*) PaintGlue::setTextScaleX},
     {"getTextSkewX","()F", (void*) PaintGlue::getTextSkewX},
     {"setTextSkewX","(F)V", (void*) PaintGlue::setTextSkewX},
+    {"native_getLetterSpacing","(J)F", (void*) PaintGlue::getLetterSpacing},
+    {"native_setLetterSpacing","(JF)V", (void*) PaintGlue::setLetterSpacing},
     {"ascent","()F", (void*) PaintGlue::ascent},
     {"descent","()F", (void*) PaintGlue::descent},
     {"getFontMetrics", "(Landroid/graphics/Paint$FontMetrics;)F", (void*)PaintGlue::getFontMetrics},
diff --git a/core/jni/android/graphics/Paint.h b/core/jni/android/graphics/Paint.h
index 239b217..7235cc4 100644
--- a/core/jni/android/graphics/Paint.h
+++ b/core/jni/android/graphics/Paint.h
@@ -34,7 +34,16 @@
         return !(a == b);
     }
 
+    void setLetterSpacing(float letterSpacing) {
+        mLetterSpacing = letterSpacing;
+    }
+
+    float getLetterSpacing() const {
+        return mLetterSpacing;
+    }
+
 private:
+    float mLetterSpacing;
 };
 
 }  // namespace android
diff --git a/core/jni/android/graphics/PaintImpl.cpp b/core/jni/android/graphics/PaintImpl.cpp
index 6baae76..ff2bbc5 100644
--- a/core/jni/android/graphics/PaintImpl.cpp
+++ b/core/jni/android/graphics/PaintImpl.cpp
@@ -22,10 +22,12 @@
 
 namespace android {
 
-Paint::Paint() : SkPaint() {
+Paint::Paint() : SkPaint(),
+        mLetterSpacing(0) {
 }
 
-Paint::Paint(const Paint& paint) : SkPaint(paint) {
+Paint::Paint(const Paint& paint) : SkPaint(paint),
+        mLetterSpacing(0) {
 }
 
 Paint::~Paint() {
@@ -33,11 +35,13 @@
 
 Paint& Paint::operator=(const Paint& other) {
     SkPaint::operator=(other);
+    mLetterSpacing = other.mLetterSpacing;
     return *this;
 }
 
 bool operator==(const Paint& a, const Paint& b) {
-    return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b);
+    return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b)
+            && a.mLetterSpacing == b.mLetterSpacing;
 }
 
 }
diff --git a/core/res/res/drawable/btn_radio_material_anim.xml b/core/res/res/drawable/btn_radio_material_anim.xml
index 0be590e..121e544 100644
--- a/core/res/res/drawable/btn_radio_material_anim.xml
+++ b/core/res/res/drawable/btn_radio_material_anim.xml
@@ -16,42 +16,52 @@
 
 <animated-selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_enabled="false" android:state_checked="true">
-        <bitmap android:src="@drawable/btn_radio_to_on_mtrl_015" android:tint="?attr/colorControlActivated" android:alpha="?attr/disabledAlpha" />
+        <bitmap
+            android:src="@drawable/btn_radio_to_on_mtrl_015"
+            android:tint="?attr/colorControlActivated"
+            android:alpha="?attr/disabledAlpha" />
     </item>
     <item android:state_enabled="false">
-        <bitmap android:src="@drawable/btn_radio_to_on_mtrl_000" android:tint="?attr/colorControlNormal" android:alpha="?attr/disabledAlpha" />
+        <bitmap
+            android:src="@drawable/btn_radio_to_on_mtrl_000"
+            android:tint="?attr/colorControlNormal"
+            android:alpha="?attr/disabledAlpha" />
     </item>
     <item android:state_checked="true" android:id="@+id/on">
-        <bitmap android:src="@drawable/btn_radio_to_on_mtrl_015" android:tint="?attr/colorControlActivated" />
+        <bitmap
+            android:src="@drawable/btn_radio_to_on_mtrl_015"
+            android:tint="?attr/colorControlActivated" />
     </item>
     <item android:id="@+id/off">
-        <bitmap android:src="@drawable/btn_radio_to_on_mtrl_000" android:tint="?attr/colorControlNormal" />
+        <bitmap
+            android:src="@drawable/btn_radio_to_on_mtrl_000"
+            android:tint="?attr/colorControlNormal" />
     </item>
     <transition android:fromId="@+id/off" android:toId="@+id/on">
         <animation-list>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_radio_to_on_mtrl_000" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_radio_to_on_mtrl_000" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_radio_to_on_mtrl_001" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_radio_to_on_mtrl_001" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_radio_to_on_mtrl_002" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_radio_to_on_mtrl_002" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_radio_to_on_mtrl_003" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_radio_to_on_mtrl_003" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_radio_to_on_mtrl_004" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_radio_to_on_mtrl_004" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_radio_to_on_mtrl_005" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_radio_to_on_mtrl_005" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_radio_to_on_mtrl_006" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_radio_to_on_mtrl_006" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_radio_to_on_mtrl_007" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_radio_to_on_mtrl_007" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
                 <bitmap android:src="@drawable/btn_radio_to_on_mtrl_008" android:tint="?attr/colorControlActivated" />
@@ -106,28 +116,28 @@
                 <bitmap android:src="@drawable/btn_radio_to_off_mtrl_007" android:tint="?attr/colorControlActivated" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_radio_to_off_mtrl_008" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_radio_to_off_mtrl_008" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_radio_to_off_mtrl_009" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_radio_to_off_mtrl_009" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_radio_to_off_mtrl_010" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_radio_to_off_mtrl_010" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_radio_to_off_mtrl_011" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_radio_to_off_mtrl_011" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_radio_to_off_mtrl_012" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_radio_to_off_mtrl_012" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_radio_to_off_mtrl_013" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_radio_to_off_mtrl_013" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_radio_to_off_mtrl_014" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_radio_to_off_mtrl_014" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_radio_to_off_mtrl_015" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_radio_to_off_mtrl_015" android:tint="?attr/colorControlNormal" />
             </item>
         </animation-list>
     </transition>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 2782b52..0e87893 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3755,6 +3755,8 @@
         <attr name="shadowRadius" format="float" />
         <!-- Elegant text height, especially for less compacted complex script text. -->
         <attr name="elegantTextHeight" format="boolean" />
+        <!-- @hide Text letter-spacing. -->
+        <attr name="letterSpacing" format="float" />
     </declare-styleable>
     <declare-styleable name="TextClock">
         <!-- Specifies the formatting pattern used to show the time and/or date
@@ -4048,6 +4050,8 @@
         <attr name="textAllCaps" />
         <!-- Elegant text height, especially for less compacted complex script text. -->
         <attr name="elegantTextHeight" />
+        <!-- @hide Text letter-spacing. -->
+        <attr name="letterSpacing" />
     </declare-styleable>
     <declare-styleable name="TextViewAppearance">
         <!-- Base text color, typeface, size, and style. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index a6e85e9..b1902dd 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2268,6 +2268,7 @@
   <public type="attr" name="checkMarkTintMode" />
   <public type="attr" name="popupTheme" />
   <public type="attr" name="toolbarStyle" />
+  <public type="attr" name="letterSpacing" />
 
   <public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
 
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 3f73a03..58a1bf2 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1260,6 +1260,29 @@
     public native void setTextSkewX(float skewX);
 
     /**
+     * Return the paint's letter-spacing for text. The default value
+     * is 0.
+     *
+     * @return         the paint's letter-spacing for drawing text.
+     * @hide
+     */
+    public float getLetterSpacing() {
+        return native_getLetterSpacing(mNativePaint);
+    }
+
+    /**
+     * Set the paint's letter-spacing for text. The default value
+     * is 0.  The value is in 'EM' units.  Typical values for slight
+     * expansion will be around 0.05.  Negative values tighten text.
+     *
+     * @param letterSpacing set the paint's letter-spacing for drawing text.
+     * @hide
+     */
+    public void setLetterSpacing(float letterSpacing) {
+        native_setLetterSpacing(mNativePaint, letterSpacing);
+    }
+
+    /**
      * Return the distance above (negative) the baseline (ascent) based on the
      * current typeface and text size.
      *
@@ -2232,4 +2255,8 @@
     private static native void native_setShadowLayer(long native_object,
             float radius, float dx, float dy, int color);
     private static native boolean native_hasShadowLayer(long native_object);
+
+    private static native float native_getLetterSpacing(long native_object);
+    private static native void native_setLetterSpacing(long native_object,
+                                                       float letterSpacing);
 }
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index 2d49365..6f21f2e 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -28,7 +28,6 @@
 import android.util.MathUtils;
 import android.view.HardwareCanvas;
 import android.view.RenderNodeAnimator;
-import android.view.animation.DecelerateInterpolator;
 import android.view.animation.LinearInterpolator;
 
 import java.util.ArrayList;
@@ -44,16 +43,14 @@
     private static final float WAVE_TOUCH_DOWN_ACCELERATION = 1024.0f * GLOBAL_SPEED;
     private static final float WAVE_TOUCH_UP_ACCELERATION = 3400.0f * GLOBAL_SPEED;
     private static final float WAVE_OPACITY_DECAY_VELOCITY = 3.0f / GLOBAL_SPEED;
-    private static final float WAVE_OUTER_OPACITY_VELOCITY_MAX = 4.5f * GLOBAL_SPEED;
-    private static final float WAVE_OUTER_OPACITY_VELOCITY_MIN = 1.5f * GLOBAL_SPEED;
-    private static final float WAVE_OUTER_SIZE_INFLUENCE_MAX = 200f;
-    private static final float WAVE_OUTER_SIZE_INFLUENCE_MIN = 40f;
 
     private static final long RIPPLE_ENTER_DELAY = 80;
 
     // Hardware animators.
-    private final ArrayList<RenderNodeAnimator> mRunningAnimations = new ArrayList<>();
-    private final ArrayList<RenderNodeAnimator> mPendingAnimations = new ArrayList<>();
+    private final ArrayList<RenderNodeAnimator> mRunningAnimations =
+            new ArrayList<RenderNodeAnimator>();
+    private final ArrayList<RenderNodeAnimator> mPendingAnimations =
+            new ArrayList<RenderNodeAnimator>();
 
     private final RippleDrawable mOwner;
 
@@ -79,20 +76,17 @@
     private CanvasProperty<Float> mPropRadius;
     private CanvasProperty<Float> mPropX;
     private CanvasProperty<Float> mPropY;
-    private CanvasProperty<Paint> mPropOuterPaint;
-    private CanvasProperty<Float> mPropOuterRadius;
-    private CanvasProperty<Float> mPropOuterX;
-    private CanvasProperty<Float> mPropOuterY;
 
     // Software animators.
     private ObjectAnimator mAnimRadius;
     private ObjectAnimator mAnimOpacity;
-    private ObjectAnimator mAnimOuterOpacity;
     private ObjectAnimator mAnimX;
     private ObjectAnimator mAnimY;
 
+    // Temporary paint used for creating canvas properties.
+    private Paint mTempPaint;
+
     // Software rendering properties.
-    private float mOuterOpacity = 0;
     private float mOpacity = 1;
     private float mOuterX;
     private float mOuterY;
@@ -177,38 +171,35 @@
         return mOpacity;
     }
 
-    public void setOuterOpacity(float a) {
-        mOuterOpacity = a;
-        invalidateSelf();
-    }
-
-    public float getOuterOpacity() {
-        return mOuterOpacity;
-    }
-
+    @SuppressWarnings("unused")
     public void setRadiusGravity(float r) {
         mTweenRadius = r;
         invalidateSelf();
     }
 
+    @SuppressWarnings("unused")
     public float getRadiusGravity() {
         return mTweenRadius;
     }
 
+    @SuppressWarnings("unused")
     public void setXGravity(float x) {
         mTweenX = x;
         invalidateSelf();
     }
 
+    @SuppressWarnings("unused")
     public float getXGravity() {
         return mTweenX;
     }
 
+    @SuppressWarnings("unused")
     public void setYGravity(float y) {
         mTweenY = y;
         invalidateSelf();
     }
 
+    @SuppressWarnings("unused")
     public float getYGravity() {
         return mTweenY;
     }
@@ -238,7 +229,7 @@
         // If we have any pending hardware animations, cancel any running
         // animations and start those now.
         final ArrayList<RenderNodeAnimator> pendingAnimations = mPendingAnimations;
-        final int N = pendingAnimations == null ? 0 : pendingAnimations.size();
+        final int N = pendingAnimations.size();
         if (N > 0) {
             cancelHardwareAnimations();
 
@@ -251,7 +242,6 @@
             pendingAnimations.clear();
         }
 
-        c.drawCircle(mPropOuterX, mPropOuterY, mPropOuterRadius, mPropOuterPaint);
         c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
 
         return true;
@@ -262,15 +252,6 @@
 
         // Cache the paint alpha so we can restore it later.
         final int paintAlpha = p.getAlpha();
-
-        final int outerAlpha = (int) (paintAlpha * mOuterOpacity + 0.5f);
-        if (outerAlpha > 0 && mOuterRadius > 0) {
-            p.setAlpha(outerAlpha);
-            p.setStyle(Style.FILL);
-            c.drawCircle(mOuterX, mOuterY, mOuterRadius, p);
-            hasContent = true;
-        }
-
         final int alpha = (int) (paintAlpha * mOpacity + 0.5f);
         final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
         if (alpha > 0 && radius > 0) {
@@ -316,7 +297,6 @@
     public void enter() {
         final int radiusDuration = (int)
                 (1000 * Math.sqrt(mOuterRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5);
-        final int outerDuration = (int) (1000 * 1.0f / WAVE_OUTER_OPACITY_VELOCITY_MIN);
 
         final ObjectAnimator radius = ObjectAnimator.ofFloat(this, "radiusGravity", 1);
         radius.setAutoCancel(true);
@@ -336,13 +316,7 @@
         cY.setInterpolator(LINEAR_INTERPOLATOR);
         cY.setStartDelay(RIPPLE_ENTER_DELAY);
 
-        final ObjectAnimator outer = ObjectAnimator.ofFloat(this, "outerOpacity", 0, 1);
-        outer.setAutoCancel(true);
-        outer.setDuration(outerDuration);
-        outer.setInterpolator(LINEAR_INTERPOLATOR);
-
         mAnimRadius = radius;
-        mAnimOuterOpacity = outer;
         mAnimX = cX;
         mAnimY = cY;
 
@@ -350,7 +324,6 @@
         // that anything interesting is happening until the user lifts their
         // finger.
         radius.start();
-        outer.start();
         cX.start();
         cY.start();
     }
@@ -372,51 +345,23 @@
                 + WAVE_TOUCH_DOWN_ACCELERATION) * mDensity) + 0.5);
         final int opacityDuration = (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
 
-        // Scale the outer max opacity and opacity velocity based
-        // on the size of the outer radius
-
-        float outerSizeInfluence = MathUtils.constrain(
-                (mOuterRadius - WAVE_OUTER_SIZE_INFLUENCE_MIN * mDensity)
-                / (WAVE_OUTER_SIZE_INFLUENCE_MAX * mDensity), 0, 1);
-        float outerOpacityVelocity = MathUtils.lerp(WAVE_OUTER_OPACITY_VELOCITY_MIN,
-                WAVE_OUTER_OPACITY_VELOCITY_MAX, outerSizeInfluence);
-
-        // Determine at what time the inner and outer opacity intersect.
-        // inner(t) = mOpacity - t * WAVE_OPACITY_DECAY_VELOCITY / 1000
-        // outer(t) = mOuterOpacity + t * WAVE_OUTER_OPACITY_VELOCITY / 1000
-
-        final int outerInflection = Math.max(0, (int) (1000 * (mOpacity - mOuterOpacity)
-                / (WAVE_OPACITY_DECAY_VELOCITY + outerOpacityVelocity) + 0.5f));
-        final int inflectionOpacity = (int) (255 * (mOuterOpacity + outerInflection
-                * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f);
-
         if (mCanUseHardware) {
-            exitHardware(radiusDuration, opacityDuration, outerInflection, inflectionOpacity);
+            exitHardware(radiusDuration, opacityDuration);
         } else {
-            exitSoftware(radiusDuration, opacityDuration, outerInflection, inflectionOpacity);
+            exitSoftware(radiusDuration, opacityDuration);
         }
     }
 
-    private void exitHardware(int radiusDuration, int opacityDuration, int outerInflection,
-            int inflectionOpacity) {
+    private void exitHardware(int radiusDuration, int opacityDuration) {
         mPendingAnimations.clear();
 
         final float startX = MathUtils.lerp(
                 mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
         final float startY = MathUtils.lerp(
                 mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
-        final Paint outerPaint = new Paint();
-        outerPaint.setAntiAlias(true);
-        outerPaint.setColor(mColor);
-        outerPaint.setAlpha((int) (255 * mOuterOpacity + 0.5f));
-        outerPaint.setStyle(Style.FILL);
-        mPropOuterPaint = CanvasProperty.createPaint(outerPaint);
-        mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius);
-        mPropOuterX = CanvasProperty.createFloat(mOuterX);
-        mPropOuterY = CanvasProperty.createFloat(mOuterY);
 
         final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
-        final Paint paint = new Paint();
+        final Paint paint = getTempPaint();
         paint.setAntiAlias(true);
         paint.setColor(mColor);
         paint.setAlpha((int) (255 * mOpacity + 0.5f));
@@ -442,41 +387,10 @@
                 RenderNodeAnimator.PAINT_ALPHA, 0);
         opacityAnim.setDuration(opacityDuration);
         opacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
-
-        final RenderNodeAnimator outerOpacityAnim;
-        if (outerInflection > 0) {
-            // Outer opacity continues to increase for a bit.
-            outerOpacityAnim = new RenderNodeAnimator(
-                    mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, inflectionOpacity);
-            outerOpacityAnim.setDuration(outerInflection);
-            outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
-
-            // Chain the outer opacity exit animation.
-            final int outerDuration = opacityDuration - outerInflection;
-            if (outerDuration > 0) {
-                final RenderNodeAnimator outerFadeOutAnim = new RenderNodeAnimator(
-                        mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
-                outerFadeOutAnim.setDuration(outerDuration);
-                outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
-                outerFadeOutAnim.setStartDelay(outerInflection);
-                outerFadeOutAnim.setStartValue(inflectionOpacity);
-                outerFadeOutAnim.addListener(mAnimationListener);
-
-                mPendingAnimations.add(outerFadeOutAnim);
-            } else {
-                outerOpacityAnim.addListener(mAnimationListener);
-            }
-        } else {
-            outerOpacityAnim = new RenderNodeAnimator(
-                    mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
-            outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
-            outerOpacityAnim.setDuration(opacityDuration);
-            outerOpacityAnim.addListener(mAnimationListener);
-        }
+        opacityAnim.addListener(mAnimationListener);
 
         mPendingAnimations.add(radiusAnim);
         mPendingAnimations.add(opacityAnim);
-        mPendingAnimations.add(outerOpacityAnim);
         mPendingAnimations.add(xAnim);
         mPendingAnimations.add(yAnim);
 
@@ -485,8 +399,14 @@
         invalidateSelf();
     }
 
-    private void exitSoftware(int radiusDuration, int opacityDuration, int outerInflection,
-            int inflectionOpacity) {
+    private Paint getTempPaint() {
+        if (mTempPaint == null) {
+            mTempPaint = new Paint();
+        }
+        return mTempPaint;
+    }
+
+    private void exitSoftware(int radiusDuration, int opacityDuration) {
         final ObjectAnimator radiusAnim = ObjectAnimator.ofFloat(this, "radiusGravity", 1);
         radiusAnim.setAutoCancel(true);
         radiusAnim.setDuration(radiusDuration);
@@ -506,58 +426,15 @@
         opacityAnim.setAutoCancel(true);
         opacityAnim.setDuration(opacityDuration);
         opacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
-
-        final ObjectAnimator outerOpacityAnim;
-        if (outerInflection > 0) {
-            // Outer opacity continues to increase for a bit.
-            outerOpacityAnim = ObjectAnimator.ofFloat(this,
-                    "outerOpacity", inflectionOpacity / 255.0f);
-            outerOpacityAnim.setAutoCancel(true);
-            outerOpacityAnim.setDuration(outerInflection);
-            outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
-
-            // Chain the outer opacity exit animation.
-            final int outerDuration = opacityDuration - outerInflection;
-            if (outerDuration > 0) {
-                outerOpacityAnim.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        final ObjectAnimator outerFadeOutAnim = ObjectAnimator.ofFloat(Ripple.this,
-                                "outerOpacity", 0);
-                        outerFadeOutAnim.setAutoCancel(true);
-                        outerFadeOutAnim.setDuration(outerDuration);
-                        outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
-                        outerFadeOutAnim.addListener(mAnimationListener);
-
-                        mAnimOuterOpacity = outerFadeOutAnim;
-
-                        outerFadeOutAnim.start();
-                    }
-
-                    @Override
-                    public void onAnimationCancel(Animator animation) {
-                        animation.removeListener(this);
-                    }
-                });
-            } else {
-                outerOpacityAnim.addListener(mAnimationListener);
-            }
-        } else {
-            outerOpacityAnim = ObjectAnimator.ofFloat(this, "outerOpacity", 0);
-            outerOpacityAnim.setAutoCancel(true);
-            outerOpacityAnim.setDuration(opacityDuration);
-            outerOpacityAnim.addListener(mAnimationListener);
-        }
+        opacityAnim.addListener(mAnimationListener);
 
         mAnimRadius = radiusAnim;
         mAnimOpacity = opacityAnim;
-        mAnimOuterOpacity = outerOpacityAnim;
-        mAnimX = opacityAnim;
-        mAnimY = opacityAnim;
+        mAnimX = xAnim;
+        mAnimY = yAnim;
 
         radiusAnim.start();
         opacityAnim.start();
-        outerOpacityAnim.start();
         xAnim.start();
         yAnim.start();
     }
@@ -579,10 +456,6 @@
             mAnimOpacity.cancel();
         }
 
-        if (mAnimOuterOpacity != null) {
-            mAnimOuterOpacity.cancel();
-        }
-
         if (mAnimX != null) {
             mAnimX.cancel();
         }
@@ -597,7 +470,7 @@
      */
     private void cancelHardwareAnimations() {
         final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations;
-        final int N = runningAnimations == null ? 0 : runningAnimations.size();
+        final int N = runningAnimations.size();
         for (int i = 0; i < N; i++) {
             runningAnimations.get(i).cancel();
         }
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
new file mode 100644
index 0000000..d404ccd
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -0,0 +1,535 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.drawable;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.graphics.Canvas;
+import android.graphics.CanvasProperty;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Rect;
+import android.util.MathUtils;
+import android.view.HardwareCanvas;
+import android.view.RenderNodeAnimator;
+import android.view.animation.LinearInterpolator;
+
+import java.util.ArrayList;
+
+/**
+ * Draws a Material ripple.
+ */
+class RippleBackground {
+    private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
+    private static final TimeInterpolator DECEL_INTERPOLATOR = new LogInterpolator();
+
+    private static final float GLOBAL_SPEED = 1.0f;
+    private static final float WAVE_TOUCH_DOWN_ACCELERATION = 1024.0f * GLOBAL_SPEED;
+    private static final float WAVE_OPACITY_DECAY_VELOCITY = 3.0f / GLOBAL_SPEED;
+    private static final float WAVE_OUTER_OPACITY_VELOCITY_MAX = 4.5f * GLOBAL_SPEED;
+    private static final float WAVE_OUTER_OPACITY_VELOCITY_MIN = 1.5f * GLOBAL_SPEED;
+    private static final float WAVE_OUTER_SIZE_INFLUENCE_MAX = 200f;
+    private static final float WAVE_OUTER_SIZE_INFLUENCE_MIN = 40f;
+
+    private static final long RIPPLE_ENTER_DELAY = 80;
+
+    // Hardware animators.
+    private final ArrayList<RenderNodeAnimator> mRunningAnimations =
+            new ArrayList<RenderNodeAnimator>();
+    private final ArrayList<RenderNodeAnimator> mPendingAnimations =
+            new ArrayList<RenderNodeAnimator>();
+
+    private final RippleDrawable mOwner;
+
+    /** Bounds used for computing max radius. */
+    private final Rect mBounds;
+
+    /** Full-opacity color for drawing this ripple. */
+    private int mColor;
+
+    /** Maximum ripple radius. */
+    private float mOuterRadius;
+
+    /** Screen density used to adjust pixel-based velocities. */
+    private float mDensity;
+
+    private float mStartingX;
+    private float mStartingY;
+    private float mClampedStartingX;
+    private float mClampedStartingY;
+
+    // Hardware rendering properties.
+    private CanvasProperty<Paint> mPropOuterPaint;
+    private CanvasProperty<Float> mPropOuterRadius;
+    private CanvasProperty<Float> mPropOuterX;
+    private CanvasProperty<Float> mPropOuterY;
+
+    // Software animators.
+    private ObjectAnimator mAnimOuterOpacity;
+    private ObjectAnimator mAnimX;
+    private ObjectAnimator mAnimY;
+
+    // Temporary paint used for creating canvas properties.
+    private Paint mTempPaint;
+
+    // Software rendering properties.
+    private float mOuterOpacity = 0;
+    private float mOuterX;
+    private float mOuterY;
+
+    // Values used to tween between the start and end positions.
+    private float mTweenX = 0;
+    private float mTweenY = 0;
+
+    /** Whether we should be drawing hardware animations. */
+    private boolean mHardwareAnimating;
+
+    /** Whether we can use hardware acceleration for the exit animation. */
+    private boolean mCanUseHardware;
+
+    /** Whether we have an explicit maximum radius. */
+    private boolean mHasMaxRadius;
+
+    /**
+     * Creates a new ripple.
+     */
+    public RippleBackground(RippleDrawable owner, Rect bounds, float startingX, float startingY) {
+        mOwner = owner;
+        mBounds = bounds;
+
+        mStartingX = startingX;
+        mStartingY = startingY;
+    }
+
+    public void setup(int maxRadius, int color, float density) {
+        mColor = color | 0xFF000000;
+
+        if (maxRadius != RippleDrawable.RADIUS_AUTO) {
+            mHasMaxRadius = true;
+            mOuterRadius = maxRadius;
+        } else {
+            final float halfWidth = mBounds.width() / 2.0f;
+            final float halfHeight = mBounds.height() / 2.0f;
+            mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
+        }
+
+        mOuterX = 0;
+        mOuterY = 0;
+        mDensity = density;
+
+        clampStartingPosition();
+    }
+
+    private void clampStartingPosition() {
+        final float cX = mBounds.exactCenterX();
+        final float cY = mBounds.exactCenterY();
+        final float dX = mStartingX - cX;
+        final float dY = mStartingY - cY;
+        final float r = mOuterRadius;
+        if (dX * dX + dY * dY > r * r) {
+            // Point is outside the circle, clamp to the circumference.
+            final double angle = Math.atan2(dY, dX);
+            mClampedStartingX = cX + (float) (Math.cos(angle) * r);
+            mClampedStartingY = cY + (float) (Math.sin(angle) * r);
+        } else {
+            mClampedStartingX = mStartingX;
+            mClampedStartingY = mStartingY;
+        }
+    }
+
+    public void onHotspotBoundsChanged() {
+        if (!mHasMaxRadius) {
+            final float halfWidth = mBounds.width() / 2.0f;
+            final float halfHeight = mBounds.height() / 2.0f;
+            mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
+
+            clampStartingPosition();
+        }
+    }
+
+    @SuppressWarnings("unused")
+    public void setOuterOpacity(float a) {
+        mOuterOpacity = a;
+        invalidateSelf();
+    }
+
+    @SuppressWarnings("unused")
+    public float getOuterOpacity() {
+        return mOuterOpacity;
+    }
+
+    @SuppressWarnings("unused")
+    public void setXGravity(float x) {
+        mTweenX = x;
+        invalidateSelf();
+    }
+
+    @SuppressWarnings("unused")
+    public float getXGravity() {
+        return mTweenX;
+    }
+
+    @SuppressWarnings("unused")
+    public void setYGravity(float y) {
+        mTweenY = y;
+        invalidateSelf();
+    }
+
+    @SuppressWarnings("unused")
+    public float getYGravity() {
+        return mTweenY;
+    }
+
+    /**
+     * Draws the ripple centered at (0,0) using the specified paint.
+     */
+    public boolean draw(Canvas c, Paint p) {
+        final boolean canUseHardware = c.isHardwareAccelerated();
+        if (mCanUseHardware != canUseHardware && mCanUseHardware) {
+            // We've switched from hardware to non-hardware mode. Panic.
+            cancelHardwareAnimations();
+        }
+        mCanUseHardware = canUseHardware;
+
+        final boolean hasContent;
+        if (canUseHardware && mHardwareAnimating) {
+            hasContent = drawHardware((HardwareCanvas) c);
+        } else {
+            hasContent = drawSoftware(c, p);
+        }
+
+        return hasContent;
+    }
+
+    private boolean drawHardware(HardwareCanvas c) {
+        // If we have any pending hardware animations, cancel any running
+        // animations and start those now.
+        final ArrayList<RenderNodeAnimator> pendingAnimations = mPendingAnimations;
+        final int N = pendingAnimations.size();
+        if (N > 0) {
+            cancelHardwareAnimations();
+
+            for (int i = 0; i < N; i++) {
+                pendingAnimations.get(i).setTarget(c);
+                pendingAnimations.get(i).start();
+            }
+
+            mRunningAnimations.addAll(pendingAnimations);
+            pendingAnimations.clear();
+        }
+
+        c.drawCircle(mPropOuterX, mPropOuterY, mPropOuterRadius, mPropOuterPaint);
+
+        return true;
+    }
+
+    private boolean drawSoftware(Canvas c, Paint p) {
+        boolean hasContent = false;
+
+        // Cache the paint alpha so we can restore it later.
+        final int paintAlpha = p.getAlpha();
+
+        final int outerAlpha = (int) (paintAlpha * mOuterOpacity + 0.5f);
+        if (outerAlpha > 0 && mOuterRadius > 0) {
+            p.setAlpha(outerAlpha);
+            p.setStyle(Style.FILL);
+            c.drawCircle(mOuterX, mOuterY, mOuterRadius, p);
+            hasContent = true;
+        }
+
+        p.setAlpha(paintAlpha);
+
+        return hasContent;
+    }
+
+    /**
+     * Returns the maximum bounds of the ripple relative to the ripple center.
+     */
+    public void getBounds(Rect bounds) {
+        final int outerX = (int) mOuterX;
+        final int outerY = (int) mOuterY;
+        final int r = (int) mOuterRadius;
+        bounds.set(outerX - r, outerY - r, outerX + r, outerY + r);
+    }
+
+    /**
+     * Specifies the starting position relative to the drawable bounds. No-op if
+     * the ripple has already entered.
+     */
+    public void move(float x, float y) {
+        mStartingX = x;
+        mStartingY = y;
+
+        clampStartingPosition();
+    }
+
+    /**
+     * Starts the enter animation.
+     */
+    public void enter() {
+        final int radiusDuration = (int)
+                (1000 * Math.sqrt(mOuterRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5);
+        final int outerDuration = (int) (1000 * 1.0f / WAVE_OUTER_OPACITY_VELOCITY_MIN);
+
+        final ObjectAnimator cX = ObjectAnimator.ofFloat(this, "xGravity", 1);
+        cX.setAutoCancel(true);
+        cX.setDuration(radiusDuration);
+        cX.setInterpolator(LINEAR_INTERPOLATOR);
+        cX.setStartDelay(RIPPLE_ENTER_DELAY);
+
+        final ObjectAnimator cY = ObjectAnimator.ofFloat(this, "yGravity", 1);
+        cY.setAutoCancel(true);
+        cY.setDuration(radiusDuration);
+        cY.setInterpolator(LINEAR_INTERPOLATOR);
+        cY.setStartDelay(RIPPLE_ENTER_DELAY);
+
+        final ObjectAnimator outer = ObjectAnimator.ofFloat(this, "outerOpacity", 0, 1);
+        outer.setAutoCancel(true);
+        outer.setDuration(outerDuration);
+        outer.setInterpolator(LINEAR_INTERPOLATOR);
+
+        mAnimOuterOpacity = outer;
+        mAnimX = cX;
+        mAnimY = cY;
+
+        // Enter animations always run on the UI thread, since it's unlikely
+        // that anything interesting is happening until the user lifts their
+        // finger.
+        outer.start();
+        cX.start();
+        cY.start();
+    }
+
+    /**
+     * Starts the exit animation.
+     */
+    public void exit() {
+        cancelSoftwareAnimations();
+
+        // Scale the outer max opacity and opacity velocity based
+        // on the size of the outer radius.
+        final int opacityDuration = (int) (1000 / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
+        final float outerSizeInfluence = MathUtils.constrain(
+                (mOuterRadius - WAVE_OUTER_SIZE_INFLUENCE_MIN * mDensity)
+                / (WAVE_OUTER_SIZE_INFLUENCE_MAX * mDensity), 0, 1);
+        final float outerOpacityVelocity = MathUtils.lerp(WAVE_OUTER_OPACITY_VELOCITY_MIN,
+                WAVE_OUTER_OPACITY_VELOCITY_MAX, outerSizeInfluence);
+
+        // Determine at what time the inner and outer opacity intersect.
+        // inner(t) = mOpacity - t * WAVE_OPACITY_DECAY_VELOCITY / 1000
+        // outer(t) = mOuterOpacity + t * WAVE_OUTER_OPACITY_VELOCITY / 1000
+        final int outerInflection = Math.max(0, (int) (1000 * (1 - mOuterOpacity)
+                / (WAVE_OPACITY_DECAY_VELOCITY + outerOpacityVelocity) + 0.5f));
+        final int inflectionOpacity = (int) (255 * (mOuterOpacity + outerInflection
+                * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f);
+
+        if (mCanUseHardware) {
+            exitHardware(opacityDuration, outerInflection, inflectionOpacity);
+        } else {
+            exitSoftware(opacityDuration, outerInflection, inflectionOpacity);
+        }
+    }
+
+    private void exitHardware(int opacityDuration, int outerInflection, int inflectionOpacity) {
+        mPendingAnimations.clear();
+
+        // TODO: Adjust background by starting position.
+        final float startX = MathUtils.lerp(
+                mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
+        final float startY = MathUtils.lerp(
+                mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
+
+        final Paint outerPaint = getTempPaint();
+        outerPaint.setAntiAlias(true);
+        outerPaint.setColor(mColor);
+        outerPaint.setAlpha((int) (255 * mOuterOpacity + 0.5f));
+        outerPaint.setStyle(Style.FILL);
+        mPropOuterPaint = CanvasProperty.createPaint(outerPaint);
+        mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius);
+        mPropOuterX = CanvasProperty.createFloat(mOuterX);
+        mPropOuterY = CanvasProperty.createFloat(mOuterY);
+
+        final RenderNodeAnimator outerOpacityAnim;
+        if (outerInflection > 0) {
+            // Outer opacity continues to increase for a bit.
+            outerOpacityAnim = new RenderNodeAnimator(
+                    mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, inflectionOpacity);
+            outerOpacityAnim.setDuration(outerInflection);
+            outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
+
+            // Chain the outer opacity exit animation.
+            final int outerDuration = opacityDuration - outerInflection;
+            if (outerDuration > 0) {
+                final RenderNodeAnimator outerFadeOutAnim = new RenderNodeAnimator(
+                        mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
+                outerFadeOutAnim.setDuration(outerDuration);
+                outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
+                outerFadeOutAnim.setStartDelay(outerInflection);
+                outerFadeOutAnim.setStartValue(inflectionOpacity);
+                outerFadeOutAnim.addListener(mAnimationListener);
+
+                mPendingAnimations.add(outerFadeOutAnim);
+            } else {
+                outerOpacityAnim.addListener(mAnimationListener);
+            }
+        } else {
+            outerOpacityAnim = new RenderNodeAnimator(
+                    mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
+            outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
+            outerOpacityAnim.setDuration(opacityDuration);
+            outerOpacityAnim.addListener(mAnimationListener);
+        }
+
+        mPendingAnimations.add(outerOpacityAnim);
+
+        mHardwareAnimating = true;
+
+        invalidateSelf();
+    }
+
+    private Paint getTempPaint() {
+        if (mTempPaint == null) {
+            mTempPaint = new Paint();
+        }
+        return mTempPaint;
+    }
+
+    private void exitSoftware(int opacityDuration, int outerInflection, int inflectionOpacity) {
+        final ObjectAnimator xAnim = ObjectAnimator.ofFloat(this, "xGravity", 1);
+        xAnim.setAutoCancel(true);
+        xAnim.setDuration(opacityDuration);
+        xAnim.setInterpolator(DECEL_INTERPOLATOR);
+
+        final ObjectAnimator yAnim = ObjectAnimator.ofFloat(this, "yGravity", 1);
+        yAnim.setAutoCancel(true);
+        yAnim.setDuration(opacityDuration);
+        yAnim.setInterpolator(DECEL_INTERPOLATOR);
+
+        final ObjectAnimator outerOpacityAnim;
+        if (outerInflection > 0) {
+            // Outer opacity continues to increase for a bit.
+            outerOpacityAnim = ObjectAnimator.ofFloat(this,
+                    "outerOpacity", inflectionOpacity / 255.0f);
+            outerOpacityAnim.setAutoCancel(true);
+            outerOpacityAnim.setDuration(outerInflection);
+            outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
+
+            // Chain the outer opacity exit animation.
+            final int outerDuration = opacityDuration - outerInflection;
+            if (outerDuration > 0) {
+                outerOpacityAnim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        final ObjectAnimator outerFadeOutAnim = ObjectAnimator.ofFloat(
+                                RippleBackground.this, "outerOpacity", 0);
+                        outerFadeOutAnim.setAutoCancel(true);
+                        outerFadeOutAnim.setDuration(outerDuration);
+                        outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
+                        outerFadeOutAnim.addListener(mAnimationListener);
+
+                        mAnimOuterOpacity = outerFadeOutAnim;
+
+                        outerFadeOutAnim.start();
+                    }
+
+                    @Override
+                    public void onAnimationCancel(Animator animation) {
+                        animation.removeListener(this);
+                    }
+                });
+            } else {
+                outerOpacityAnim.addListener(mAnimationListener);
+            }
+        } else {
+            outerOpacityAnim = ObjectAnimator.ofFloat(this, "outerOpacity", 0);
+            outerOpacityAnim.setAutoCancel(true);
+            outerOpacityAnim.setDuration(opacityDuration);
+            outerOpacityAnim.addListener(mAnimationListener);
+        }
+
+        mAnimOuterOpacity = outerOpacityAnim;
+        mAnimX = xAnim;
+        mAnimY = yAnim;
+
+        outerOpacityAnim.start();
+        xAnim.start();
+        yAnim.start();
+    }
+
+    /**
+     * Cancel all animations.
+     */
+    public void cancel() {
+        cancelSoftwareAnimations();
+        cancelHardwareAnimations();
+    }
+
+    private void cancelSoftwareAnimations() {
+        if (mAnimOuterOpacity != null) {
+            mAnimOuterOpacity.cancel();
+        }
+
+        if (mAnimX != null) {
+            mAnimX.cancel();
+        }
+
+        if (mAnimY != null) {
+            mAnimY.cancel();
+        }
+    }
+
+    /**
+     * Cancels any running hardware animations.
+     */
+    private void cancelHardwareAnimations() {
+        final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations;
+        final int N = runningAnimations.size();
+        for (int i = 0; i < N; i++) {
+            runningAnimations.get(i).cancel();
+        }
+
+        runningAnimations.clear();
+    }
+
+    private void removeSelf() {
+        // The owner will invalidate itself.
+        mOwner.removeBackground(this);
+    }
+
+    private void invalidateSelf() {
+        mOwner.invalidateSelf();
+    }
+
+    private final AnimatorListenerAdapter mAnimationListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            removeSelf();
+        }
+    };
+
+    /**
+    * Interpolator with a smooth log deceleration
+    */
+    private static final class LogInterpolator implements TimeInterpolator {
+        @Override
+        public float getInterpolation(float input) {
+            return 1 - (float) Math.pow(400, -input * 1.4);
+        }
+    }
+}
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 0e719ee..0c9c558 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -120,8 +120,22 @@
     /** The masking layer, e.g. the layer with id R.id.mask. */
     private Drawable mMask;
 
-    /** The current hotspot. May be actively animating or pending entry. */
-    private Ripple mHotspot;
+    /** The current background. May be actively animating or pending entry. */
+    private RippleBackground mBackground;
+
+    /** Whether we expect to draw a background when visible. */
+    private boolean mBackgroundActive;
+
+    /** The current ripple. May be actively animating or pending entry. */
+    private Ripple mRipple;
+
+    /** Whether we expect to draw a ripple when visible. */
+    private boolean mRippleActive;
+
+    // Hotspot coordinates that are awaiting activation.
+    private float mPendingX;
+    private float mPendingY;
+    private boolean mHasPending;
 
     /**
      * Lazily-created array of actively animating ripples. Inactive ripples are
@@ -142,9 +156,6 @@
     /** Whether bounds are being overridden. */
     private boolean mOverrideBounds;
 
-    /** Whether the hotspot is currently active (e.g. focused or pressed). */
-    private boolean mActive;
-
     /**
      * Constructor used for drawable inflation.
      */
@@ -205,20 +216,26 @@
     protected boolean onStateChange(int[] stateSet) {
         super.onStateChange(stateSet);
 
-        // TODO: This would make more sense in a StateListDrawable.
-        boolean active = false;
         boolean enabled = false;
+        boolean pressed = false;
+        boolean focused = false;
+
         final int N = stateSet.length;
         for (int i = 0; i < N; i++) {
             if (stateSet[i] == R.attr.state_enabled) {
                 enabled = true;
             }
             if (stateSet[i] == R.attr.state_focused
-                    || stateSet[i] == R.attr.state_pressed) {
-                active = true;
+                    || stateSet[i] == R.attr.state_selected) {
+                focused = true;
+            }
+            if (stateSet[i] == R.attr.state_pressed) {
+                pressed = true;
             }
         }
-        setActive(active && enabled);
+
+        setRippleActive(enabled && pressed);
+        setBackgroundActive(focused || (enabled && pressed));
 
         // Update the paint color. Only applicable when animated in software.
         if (mRipplePaint != null && mState.mColor != null) {
@@ -235,14 +252,24 @@
         return false;
     }
 
-    private void setActive(boolean active) {
-        if (mActive != active) {
-            mActive = active;
-
+    private void setRippleActive(boolean active) {
+        if (mRippleActive != active) {
+            mRippleActive = active;
             if (active) {
-                activateHotspot();
+                activateRipple();
             } else {
-                removeHotspot();
+                removeRipple();
+            }
+        }
+    }
+
+    private void setBackgroundActive(boolean active) {
+        if (mBackgroundActive != active) {
+            mBackgroundActive = active;
+            if (active) {
+                activateBackground();
+            } else {
+                removeBackground();
             }
         }
     }
@@ -261,11 +288,23 @@
 
     @Override
     public boolean setVisible(boolean visible, boolean restart) {
+        final boolean changed = super.setVisible(visible, restart);
+
         if (!visible) {
             clearHotspots();
+        } else if (changed) {
+            // If we just became visible, ensure the background and ripple
+            // visibilities are consistent with their internal states.
+            if (mRippleActive) {
+                activateRipple();
+            }
+
+            if (mBackgroundActive) {
+                activateBackground();
+            }
         }
 
-        return super.setVisible(visible, restart);
+        return changed;
     }
 
     /**
@@ -399,54 +438,101 @@
 
     @Override
     public void setHotspot(float x, float y) {
-        if (mHotspot == null) {
-            mHotspot = new Ripple(this, mHotspotBounds, x, y);
+        if (mRipple == null || mBackground == null) {
+            mPendingX = x;
+            mPendingY = y;
+            mHasPending = true;
+        }
 
-            if (mActive) {
-                activateHotspot();
-            }
-        } else {
-            mHotspot.move(x, y);
+        if (mRipple != null) {
+            mRipple.move(x, y);
+        }
+
+        if (mBackground != null) {
+            mBackground.move(x, y);
         }
     }
 
     /**
      * Creates an active hotspot at the specified location.
      */
-    private void activateHotspot() {
+    private void activateBackground() {
+        if (mBackground == null) {
+            final float x;
+            final float y;
+            if (mHasPending) {
+                mHasPending = false;
+                x = mPendingX;
+                y = mPendingY;
+            } else {
+                x = mHotspotBounds.exactCenterX();
+                y = mHotspotBounds.exactCenterY();
+            }
+            mBackground = new RippleBackground(this, mHotspotBounds, x, y);
+        }
+
+        final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
+        mBackground.setup(mState.mMaxRadius, color, mDensity);
+        mBackground.enter();
+    }
+
+    private void removeBackground() {
+        if (mBackground != null) {
+            // Don't null out the background, we need it to draw!
+            mBackground.exit();
+        }
+    }
+
+    /**
+     * Creates an active hotspot at the specified location.
+     */
+    private void activateRipple() {
         if (mAnimatingRipplesCount >= MAX_RIPPLES) {
             // This should never happen unless the user is tapping like a maniac
             // or there is a bug that's preventing ripples from being removed.
             return;
         }
 
-        if (mHotspot == null) {
-            final float x = mHotspotBounds.exactCenterX();
-            final float y = mHotspotBounds.exactCenterY();
-            mHotspot = new Ripple(this, mHotspotBounds, x, y);
+        if (mRipple == null) {
+            final float x;
+            final float y;
+            if (mHasPending) {
+                mHasPending = false;
+                x = mPendingX;
+                y = mPendingY;
+            } else {
+                x = mHotspotBounds.exactCenterX();
+                y = mHotspotBounds.exactCenterY();
+            }
+            mRipple = new Ripple(this, mHotspotBounds, x, y);
         }
 
         final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
-        mHotspot.setup(mState.mMaxRadius, color, mDensity);
-        mHotspot.enter();
+        mRipple.setup(mState.mMaxRadius, color, mDensity);
+        mRipple.enter();
 
         if (mAnimatingRipples == null) {
             mAnimatingRipples = new Ripple[MAX_RIPPLES];
         }
-        mAnimatingRipples[mAnimatingRipplesCount++] = mHotspot;
+        mAnimatingRipples[mAnimatingRipplesCount++] = mRipple;
     }
 
-    private void removeHotspot() {
-        if (mHotspot != null) {
-            mHotspot.exit();
-            mHotspot = null;
+    private void removeRipple() {
+        if (mRipple != null) {
+            mRipple.exit();
+            mRipple = null;
         }
     }
 
     private void clearHotspots() {
-        if (mHotspot != null) {
-            mHotspot.cancel();
-            mHotspot = null;
+        if (mRipple != null) {
+            mRipple.cancel();
+            mRipple = null;
+        }
+
+        if (mBackground != null) {
+            mBackground.cancel();
+            mBackground = null;
         }
 
         final int count = mAnimatingRipplesCount;
@@ -486,6 +572,10 @@
         for (int i = 0; i < count; i++) {
             ripples[i].onHotspotBoundsChanged();
         }
+
+        if (mBackground != null) {
+            mBackground.onHotspotBoundsChanged();
+        }
     }
 
     /**
@@ -524,18 +614,28 @@
         // Next, try to draw the ripples (into a layer if necessary). If we need
         // to mask against the underlying content, set the xfermode to SRC_ATOP.
         final PorterDuffXfermode xfermode = (hasMask || !drawNonMaskContent) ? SRC_OVER : SRC_ATOP;
-        final int rippleLayer = drawRippleLayer(canvas, bounds, xfermode);
+
+        // If we have a background and a non-opaque mask, draw the masking layer.
+        final int backgroundLayer = drawBackgroundLayer(canvas, bounds, xfermode);
+        if (backgroundLayer >= 0) {
+            if (drawMask) {
+                drawMaskingLayer(canvas, bounds, DST_IN);
+            }
+            canvas.restoreToCount(backgroundLayer);
+        }
 
         // If we have ripples and a non-opaque mask, draw the masking layer.
-        if (rippleLayer >= 0 && drawMask) {
-            drawMaskingLayer(canvas, bounds, DST_IN);
+        final int rippleLayer = drawRippleLayer(canvas, bounds, xfermode);
+        if (rippleLayer >= 0) {
+            if (drawMask) {
+                drawMaskingLayer(canvas, bounds, DST_IN);
+            }
+            canvas.restoreToCount(rippleLayer);
         }
 
         // Composite the layers if needed.
         if (contentLayer >= 0) {
             canvas.restoreToCount(contentLayer);
-        } else if (rippleLayer >= 0) {
-            canvas.restoreToCount(rippleLayer);
         }
     }
 
@@ -550,15 +650,20 @@
         final int count = mAnimatingRipplesCount;
         final int index = getRippleIndex(ripple);
         if (index >= 0) {
-            for (int i = index + 1; i < count; i++) {
-                ripples[i - 1] = ripples[i];
-            }
+            System.arraycopy(ripples, index + 1, ripples, index + 1 - 1, count - (index + 1));
             ripples[count - 1] = null;
             mAnimatingRipplesCount--;
             invalidateSelf();
         }
     }
 
+    void removeBackground(RippleBackground background) {
+        if (mBackground == background) {
+            mBackground = null;
+            invalidateSelf();
+        }
+    }
+
     private int getRippleIndex(Ripple ripple) {
         final Ripple[] ripples = mAnimatingRipples;
         final int count = mAnimatingRipplesCount;
@@ -577,7 +682,7 @@
         // We don't need a layer if we don't expect to draw any ripples, we have
         // an explicit mask, or if the non-mask content is all opaque.
         boolean needsLayer = false;
-        if (mAnimatingRipplesCount > 0 && mMask == null) {
+        if ((mAnimatingRipplesCount > 0 || mBackground != null) && mMask == null) {
             for (int i = 0; i < count; i++) {
                 if (array[i].mId != R.id.mask
                         && array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) {
@@ -601,12 +706,62 @@
         return restoreToCount;
     }
 
-    private int drawRippleLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
-        final int count = mAnimatingRipplesCount;
-        if (count == 0) {
-            return -1;
+    private int drawBackgroundLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
+        // Separate the ripple color and alpha channel. The alpha will be
+        // applied when we merge the ripples down to the canvas.
+        final int rippleARGB;
+        if (mState.mColor != null) {
+            rippleARGB = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
+        } else {
+            rippleARGB = Color.TRANSPARENT;
         }
 
+        if (mRipplePaint == null) {
+            mRipplePaint = new Paint();
+            mRipplePaint.setAntiAlias(true);
+        }
+
+        final int rippleAlpha = Color.alpha(rippleARGB);
+        final Paint ripplePaint = mRipplePaint;
+        ripplePaint.setColor(rippleARGB);
+        ripplePaint.setAlpha(0xFF);
+
+        boolean drewRipples = false;
+        int restoreToCount = -1;
+        int restoreTranslate = -1;
+
+        // Draw background.
+        final RippleBackground background = mBackground;
+        if (background != null) {
+            // If we're masking the ripple layer, make sure we have a layer
+            // first. This will merge SRC_OVER (directly) onto the canvas.
+            final Paint maskingPaint = getMaskingPaint(mode);
+            maskingPaint.setAlpha(rippleAlpha);
+            restoreToCount = canvas.saveLayer(bounds.left, bounds.top,
+                    bounds.right, bounds.bottom, maskingPaint);
+
+            restoreTranslate = canvas.save();
+            // Translate the canvas to the current hotspot bounds.
+            canvas.translate(mHotspotBounds.exactCenterX(), mHotspotBounds.exactCenterY());
+
+            drewRipples = background.draw(canvas, ripplePaint);
+        }
+
+        // Always restore the translation.
+        if (restoreTranslate >= 0) {
+            canvas.restoreToCount(restoreTranslate);
+        }
+
+        // If we created a layer with no content, merge it immediately.
+        if (restoreToCount >= 0 && !drewRipples) {
+            canvas.restoreToCount(restoreToCount);
+            restoreToCount = -1;
+        }
+
+        return restoreToCount;
+    }
+
+    private int drawRippleLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
         // Separate the ripple color and alpha channel. The alpha will be
         // applied when we merge the ripples down to the canvas.
         final int rippleARGB;
@@ -631,6 +786,7 @@
         int restoreTranslate = -1;
 
         // Draw ripples and update the animating ripples array.
+        final int count = mAnimatingRipplesCount;
         final Ripple[] ripples = mAnimatingRipples;
         for (int i = 0; i < count; i++) {
             final Ripple ripple = ripples[i];
@@ -705,6 +861,13 @@
                 drawingBounds.union(rippleBounds);
             }
 
+            final RippleBackground background = mBackground;
+            if (background != null) {
+                background.getBounds(rippleBounds);
+                rippleBounds.offset(cX, cY);
+                drawingBounds.union(rippleBounds);
+            }
+
             dirtyBounds.union(drawingBounds);
             dirtyBounds.union(super.getDirtyBounds());
             return dirtyBounds;
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 8993af8..050c268 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -44,6 +44,7 @@
 import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
 import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiPlaybackClient;
 import android.hardware.hdmi.HdmiTvClient;
 import android.hardware.usb.UsbManager;
 import android.media.MediaPlayer.OnCompletionListener;
@@ -144,7 +145,23 @@
     private final Context mContext;
     private final ContentResolver mContentResolver;
     private final AppOpsManager mAppOps;
-    private final boolean mVoiceCapable;
+
+    // the platform has no specific capabilities
+    private static final int PLATFORM_DEFAULT = 0;
+    // the platform is voice call capable (a phone)
+    private static final int PLATFORM_VOICE = 1;
+    // the platform is a television or a set-top box
+    private static final int PLATFORM_TELEVISION = 2;
+    // the platform type affects volume and silent mode behavior
+    private final int mPlatformType;
+
+    private boolean isPlatformVoice() {
+        return mPlatformType == PLATFORM_VOICE;
+    }
+
+    private boolean isPlatformTelevision() {
+        return mPlatformType == PLATFORM_TELEVISION;
+    }
 
     /** The controller for the volume UI. */
     private final VolumeController mVolumeController = new VolumeController();
@@ -243,9 +260,10 @@
      * NOTE: do not create loops in aliases!
      * Some streams alias to different streams according to device category (phone or tablet) or
      * use case (in call vs off call...). See updateStreamVolumeAlias() for more details.
-     *  mStreamVolumeAlias contains the default aliases for a voice capable device (phone) and
-     *  STREAM_VOLUME_ALIAS_NON_VOICE for a non voice capable device (tablet).*/
-    private final int[] STREAM_VOLUME_ALIAS = new int[] {
+     *  mStreamVolumeAlias contains STREAM_VOLUME_ALIAS_VOICE aliases for a voice capable device
+     *  (phone), STREAM_VOLUME_ALIAS_TELEVISION for a television or set-top box and
+     *  STREAM_VOLUME_ALIAS_DEFAULT for other devices (e.g. tablets).*/
+    private final int[] STREAM_VOLUME_ALIAS_VOICE = new int[] {
         AudioSystem.STREAM_VOICE_CALL,      // STREAM_VOICE_CALL
         AudioSystem.STREAM_RING,            // STREAM_SYSTEM
         AudioSystem.STREAM_RING,            // STREAM_RING
@@ -257,7 +275,19 @@
         AudioSystem.STREAM_RING,            // STREAM_DTMF
         AudioSystem.STREAM_MUSIC            // STREAM_TTS
     };
-    private final int[] STREAM_VOLUME_ALIAS_NON_VOICE = new int[] {
+    private final int[] STREAM_VOLUME_ALIAS_TELEVISION = new int[] {
+        AudioSystem.STREAM_MUSIC,       // STREAM_VOICE_CALL
+        AudioSystem.STREAM_MUSIC,       // STREAM_SYSTEM
+        AudioSystem.STREAM_MUSIC,       // STREAM_RING
+        AudioSystem.STREAM_MUSIC,       // STREAM_MUSIC
+        AudioSystem.STREAM_MUSIC,       // STREAM_ALARM
+        AudioSystem.STREAM_MUSIC,       // STREAM_NOTIFICATION
+        AudioSystem.STREAM_MUSIC,       // STREAM_BLUETOOTH_SCO
+        AudioSystem.STREAM_MUSIC,       // STREAM_SYSTEM_ENFORCED
+        AudioSystem.STREAM_MUSIC,       // STREAM_DTMF
+        AudioSystem.STREAM_MUSIC        // STREAM_TTS
+    };
+    private final int[] STREAM_VOLUME_ALIAS_DEFAULT = new int[] {
         AudioSystem.STREAM_VOICE_CALL,      // STREAM_VOICE_CALL
         AudioSystem.STREAM_MUSIC,           // STREAM_SYSTEM
         AudioSystem.STREAM_RING,            // STREAM_RING
@@ -455,9 +485,12 @@
     public final static int STREAM_REMOTE_MUSIC = -200;
 
     // Devices for which the volume is fixed and VolumePanel slider should be disabled
-    final int mFixedVolumeDevices = AudioSystem.DEVICE_OUT_HDMI |
+    int mFixedVolumeDevices = AudioSystem.DEVICE_OUT_HDMI |
             AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET |
-            AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET;
+            AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET |
+            AudioSystem.DEVICE_OUT_HDMI_ARC |
+            AudioSystem.DEVICE_OUT_SPDIF |
+            AudioSystem.DEVICE_OUT_AUX_LINE;
 
     // TODO merge orientation and rotation
     private final boolean mMonitorOrientation;
@@ -491,8 +524,16 @@
         mContext = context;
         mContentResolver = context.getContentResolver();
         mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
-        mVoiceCapable = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_voice_capable);
+
+        if (mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_voice_capable)) {
+            mPlatformType = PLATFORM_VOICE;
+        } else if (context.getPackageManager().hasSystemFeature(
+                                                            PackageManager.FEATURE_TELEVISION)) {
+            mPlatformType = PLATFORM_TELEVISION;
+        } else {
+            mPlatformType = PLATFORM_DEFAULT;
+        }
 
         PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
         mAudioEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleAudioEvent");
@@ -622,10 +663,15 @@
                                     BluetoothProfile.A2DP);
         }
 
-        HdmiControlManager hdmiManager =
+        mHdmiManager =
                 (HdmiControlManager) mContext.getSystemService(Context.HDMI_CONTROL_SERVICE);
-        // Null if device is not Tv.
-        mHdmiTvClient = hdmiManager.getTvClient();
+        if (mHdmiManager != null) {
+            synchronized (mHdmiManager) {
+                mHdmiTvClient = mHdmiManager.getTvClient();
+                mHdmiPlaybackClient = mHdmiManager.getPlaybackClient();
+                mHdmiCecSink = false;
+            }
+        }
 
         sendMsg(mAudioHandler,
                 MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED,
@@ -670,6 +716,14 @@
         }
     }
 
+    private void checkAllFixedVolumeDevices()
+    {
+        int numStreamTypes = AudioSystem.getNumStreamTypes();
+        for (int streamType = 0; streamType < numStreamTypes; streamType++) {
+            mStreamStates[streamType].checkFixedVolumeDevices();
+        }
+    }
+
     private void createStreamStates() {
         int numStreamTypes = AudioSystem.getNumStreamTypes();
         VolumeStreamState[] streams = mStreamStates = new VolumeStreamState[numStreamTypes];
@@ -678,6 +732,7 @@
             streams[i] = new VolumeStreamState(System.VOLUME_SETTINGS[mStreamVolumeAlias[i]], i);
         }
 
+        checkAllFixedVolumeDevices();
         checkAllAliasStreamVolumes();
     }
 
@@ -702,19 +757,32 @@
 
     private void updateStreamVolumeAlias(boolean updateVolumes) {
         int dtmfStreamAlias;
-        if (mVoiceCapable) {
-            mStreamVolumeAlias = STREAM_VOLUME_ALIAS;
+
+        switch (mPlatformType) {
+        case PLATFORM_VOICE:
+            mStreamVolumeAlias = STREAM_VOLUME_ALIAS_VOICE;
             dtmfStreamAlias = AudioSystem.STREAM_RING;
-        } else {
-            mStreamVolumeAlias = STREAM_VOLUME_ALIAS_NON_VOICE;
+            break;
+        case PLATFORM_TELEVISION:
+            mStreamVolumeAlias = STREAM_VOLUME_ALIAS_TELEVISION;
+            dtmfStreamAlias = AudioSystem.STREAM_MUSIC;
+            break;
+        default:
+            mStreamVolumeAlias = STREAM_VOLUME_ALIAS_DEFAULT;
             dtmfStreamAlias = AudioSystem.STREAM_MUSIC;
         }
-        if (isInCommunication()) {
-            dtmfStreamAlias = AudioSystem.STREAM_VOICE_CALL;
-            mRingerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_DTMF);
+
+        if (isPlatformTelevision()) {
+            mRingerModeAffectedStreams = 0;
         } else {
-            mRingerModeAffectedStreams |= (1 << AudioSystem.STREAM_DTMF);
+            if (isInCommunication()) {
+                dtmfStreamAlias = AudioSystem.STREAM_VOICE_CALL;
+                mRingerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_DTMF);
+            } else {
+                mRingerModeAffectedStreams |= (1 << AudioSystem.STREAM_DTMF);
+            }
         }
+
         mStreamVolumeAlias[AudioSystem.STREAM_DTMF] = dtmfStreamAlias;
         if (updateVolumes) {
             mStreamStates[AudioSystem.STREAM_DTMF].setAllIndexes(mStreamStates[dtmfStreamAlias]);
@@ -768,7 +836,7 @@
         if (ringerMode != ringerModeFromSettings) {
             Settings.Global.putInt(cr, Settings.Global.MODE_RINGER, ringerMode);
         }
-        if (mUseFixedVolume) {
+        if (mUseFixedVolume || isPlatformTelevision()) {
             ringerMode = AudioManager.RINGER_MODE_NORMAL;
         }
         synchronized(mSettingsLock) {
@@ -998,15 +1066,30 @@
 
             // Check if volume update should be send to Hdmi system audio.
             int newIndex = mStreamStates[streamType].getIndex(device);
-            if (mHdmiTvClient != null &&
-                streamTypeAlias == AudioSystem.STREAM_MUSIC &&
-                (flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0 &&
-                oldIndex != newIndex) {
-                int maxIndex = getStreamMaxVolume(streamType);
-                synchronized (mHdmiTvClient) {
-                    if (mHdmiSystemAudioSupported) {
-                        mHdmiTvClient.setSystemAudioVolume(
-                                (oldIndex + 5) / 10, (newIndex + 5) / 10, maxIndex);
+            if (mHdmiManager != null) {
+                synchronized (mHdmiManager) {
+                    if (mHdmiTvClient != null &&
+                        streamTypeAlias == AudioSystem.STREAM_MUSIC &&
+                        (flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0 &&
+                        oldIndex != newIndex) {
+                        int maxIndex = getStreamMaxVolume(streamType);
+                        synchronized (mHdmiTvClient) {
+                            if (mHdmiSystemAudioSupported) {
+                                mHdmiTvClient.setSystemAudioVolume(
+                                        (oldIndex + 5) / 10, (newIndex + 5) / 10, maxIndex);
+                            }
+                        }
+                    }
+                    // mHdmiCecSink true => mHdmiPlaybackClient != null
+                    if (mHdmiCecSink &&
+                            streamTypeAlias == AudioSystem.STREAM_MUSIC &&
+                            oldIndex != newIndex) {
+                        synchronized (mHdmiPlaybackClient) {
+                            int keyCode = (direction == -1) ? KeyEvent.KEYCODE_VOLUME_DOWN :
+                                                               KeyEvent.KEYCODE_VOLUME_UP;
+                            mHdmiPlaybackClient.sendKeyEvent(keyCode, true);
+                            mHdmiPlaybackClient.sendKeyEvent(keyCode, false);
+                        }
                     }
                 }
             }
@@ -1110,15 +1193,19 @@
                 }
             }
 
-            if (mHdmiTvClient != null &&
-                streamTypeAlias == AudioSystem.STREAM_MUSIC &&
-                (flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0 &&
-                oldIndex != index) {
-                int maxIndex = getStreamMaxVolume(streamType);
-                synchronized (mHdmiTvClient) {
-                    if (mHdmiSystemAudioSupported) {
-                        mHdmiTvClient.setSystemAudioVolume(
-                                (oldIndex + 5) / 10, (index + 5) / 10, maxIndex);
+            if (mHdmiManager != null) {
+                synchronized (mHdmiManager) {
+                    if (mHdmiTvClient != null &&
+                        streamTypeAlias == AudioSystem.STREAM_MUSIC &&
+                        (flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0 &&
+                        oldIndex != index) {
+                        int maxIndex = getStreamMaxVolume(streamType);
+                        synchronized (mHdmiTvClient) {
+                            if (mHdmiSystemAudioSupported) {
+                                mHdmiTvClient.setSystemAudioVolume(
+                                        (oldIndex + 5) / 10, (index + 5) / 10, maxIndex);
+                            }
+                        }
                     }
                 }
             }
@@ -1257,7 +1344,7 @@
 
     // UI update and Broadcast Intent
     private void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags) {
-        if (!mVoiceCapable && (streamType == AudioSystem.STREAM_RING)) {
+        if (!isPlatformVoice() && (streamType == AudioSystem.STREAM_RING)) {
             streamType = AudioSystem.STREAM_NOTIFICATION;
         }
 
@@ -1346,10 +1433,14 @@
         }
 
         if (isStreamAffectedByMute(streamType)) {
-            if (streamType == AudioSystem.STREAM_MUSIC && mHdmiTvClient != null) {
-                synchronized (mHdmiTvClient) {
-                    if (mHdmiSystemAudioSupported) {
-                        mHdmiTvClient.setSystemAudioMute(state);
+            if (mHdmiManager != null) {
+                synchronized (mHdmiManager) {
+                    if (streamType == AudioSystem.STREAM_MUSIC && mHdmiTvClient != null) {
+                        synchronized (mHdmiTvClient) {
+                            if (mHdmiSystemAudioSupported) {
+                                mHdmiTvClient.setSystemAudioMute(state);
+                            }
+                        }
                     }
                 }
             }
@@ -1472,11 +1563,15 @@
 
     /** @see AudioManager#getMasterStreamType()  */
     public int getMasterStreamType() {
-        if (mVoiceCapable) {
-            return AudioSystem.STREAM_RING;
-        } else {
-            return AudioSystem.STREAM_NOTIFICATION;
+        switch (mPlatformType) {
+            case PLATFORM_VOICE:
+                return AudioSystem.STREAM_RING;
+            case PLATFORM_TELEVISION:
+                return AudioSystem.STREAM_MUSIC;
+            default:
+                break;
         }
+        return AudioSystem.STREAM_NOTIFICATION;
     }
 
     /** @see AudioManager#setMicrophoneMute(boolean) */
@@ -1504,7 +1599,7 @@
 
     /** @see AudioManager#setRingerMode(int) */
     public void setRingerMode(int ringerMode) {
-        if (mUseFixedVolume) {
+        if (mUseFixedVolume || isPlatformTelevision()) {
             return;
         }
 
@@ -1534,7 +1629,7 @@
                     ringerMode == AudioManager.RINGER_MODE_NORMAL) {
                     // ring and notifications volume should never be 0 when not silenced
                     // on voice capable devices
-                    if (mVoiceCapable &&
+                    if (isPlatformVoice() &&
                             mStreamVolumeAlias[streamType] == AudioSystem.STREAM_RING) {
                         synchronized (mStreamStates[streamType]) {
                             Set set = mStreamStates[streamType].mIndex.entrySet();
@@ -2041,6 +2136,7 @@
         // muted by ringer mode have the correct volume
         setRingerModeInt(getRingerMode(), false);
 
+        checkAllFixedVolumeDevices();
         checkAllAliasStreamVolumes();
 
         synchronized (mSafeMediaVolumeState) {
@@ -2760,11 +2856,18 @@
                                         (1 << AudioSystem.STREAM_NOTIFICATION)|
                                         (1 << AudioSystem.STREAM_SYSTEM);
 
-        if (mVoiceCapable) {
-            ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_MUSIC);
-        } else {
-            ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_MUSIC);
+        switch (mPlatformType) {
+            case PLATFORM_VOICE:
+                ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_MUSIC);
+                break;
+            case PLATFORM_TELEVISION:
+                ringerModeAffectedStreams = 0;
+                break;
+            default:
+                ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_MUSIC);
+                break;
         }
+
         synchronized (mCameraSoundForced) {
             if (mCameraSoundForced) {
                 ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
@@ -2833,7 +2936,8 @@
     }
 
     private int getActiveStreamType(int suggestedStreamType) {
-        if (mVoiceCapable) {
+        switch (mPlatformType) {
+        case PLATFORM_VOICE:
             if (isInCommunication()) {
                 if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION)
                         == AudioSystem.FORCE_BT_SCO) {
@@ -2863,12 +2967,26 @@
                 if (DEBUG_VOL)
                     Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active");
                 return AudioSystem.STREAM_MUSIC;
-            } else {
-                if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Returning suggested type "
-                        + suggestedStreamType);
-                return suggestedStreamType;
             }
-        } else {
+            break;
+        case PLATFORM_TELEVISION:
+            if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
+                if (isAfMusicActiveRecently(DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS)) {
+                    if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: forcing STREAM_MUSIC");
+                    return AudioSystem.STREAM_MUSIC;
+                } else if (mMediaFocusControl.checkUpdateRemoteStateIfActive(
+                                                                        AudioSystem.STREAM_MUSIC)) {
+                    if (DEBUG_VOL)
+                        Log.v(TAG, "getActiveStreamType: Forcing STREAM_REMOTE_MUSIC");
+                    return STREAM_REMOTE_MUSIC;
+                } else {
+                    if (DEBUG_VOL) Log.v(TAG,
+                            "getActiveStreamType: using STREAM_MUSIC as default");
+                    return AudioSystem.STREAM_MUSIC;
+                }
+            }
+            break;
+        default:
             if (isInCommunication()) {
                 if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION)
                         == AudioSystem.FORCE_BT_SCO) {
@@ -2899,12 +3017,12 @@
                             "getActiveStreamType: using STREAM_NOTIFICATION as default");
                     return AudioSystem.STREAM_NOTIFICATION;
                 }
-            } else {
-                if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Returning suggested type "
-                        + suggestedStreamType);
-                return suggestedStreamType;
             }
+            break;
         }
+        if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Returning suggested type "
+                + suggestedStreamType);
+        return suggestedStreamType;
     }
 
     private void broadcastRingerMode(int ringerMode) {
@@ -3098,13 +3216,7 @@
                         continue;
                     }
 
-                    // ignore settings for fixed volume devices: volume should always be at max or 0
-                    if ((mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) &&
-                            ((device & mFixedVolumeDevices) != 0)) {
-                        mIndex.put(device, (index != 0) ? mIndexMax : 0);
-                    } else {
-                        mIndex.put(device, getValidIndex(10 * index));
-                    }
+                    mIndex.put(device, getValidIndex(10 * index));
                 }
             }
         }
@@ -3263,6 +3375,25 @@
             return mStreamType;
         }
 
+        public void checkFixedVolumeDevices() {
+            synchronized (VolumeStreamState.class) {
+                // ignore settings for fixed volume devices: volume should always be at max or 0
+                if (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) {
+                    Set set = mIndex.entrySet();
+                    Iterator i = set.iterator();
+                    while (i.hasNext()) {
+                        Map.Entry entry = (Map.Entry)i.next();
+                        int device = ((Integer)entry.getKey()).intValue();
+                        int index = ((Integer)entry.getValue()).intValue();
+                        if (((device & mFixedVolumeDevices) != 0) && index != 0) {
+                            entry.setValue(mIndexMax);
+                        }
+                        applyDeviceVolume(device);
+                    }
+                }
+            }
+        }
+
         private int getValidIndex(int index) {
             if (index < 0) {
                 return 0;
@@ -3469,6 +3600,9 @@
             if (mUseFixedVolume) {
                 return;
             }
+            if (isPlatformTelevision() && (streamState.mStreamType != AudioSystem.STREAM_MUSIC)) {
+                return;
+            }
             System.putIntForUser(mContentResolver,
                       streamState.getSettingNameForDevice(device),
                       (streamState.getIndex(device) + 5)/ 10,
@@ -3825,11 +3959,13 @@
                                 mDockAudioMediaEnabled ?
                                         AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE);
                     }
-
-                    if (mHdmiTvClient != null) {
-                        setHdmiSystemAudioSupported(mHdmiSystemAudioSupported);
+                    if (mHdmiManager != null) {
+                        synchronized (mHdmiManager) {
+                            if (mHdmiTvClient != null) {
+                                setHdmiSystemAudioSupported(mHdmiSystemAudioSupported);
+                            }
+                        }
                     }
-
                     // indicate the end of reconfiguration phase to audio HAL
                     AudioSystem.setParameters("restarting=false");
                     break;
@@ -4275,6 +4411,27 @@
                             null,
                             MUSIC_ACTIVE_POLL_PERIOD_MS);
                 }
+                // Television devices without CEC service apply software volume on HDMI output
+                if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
+                    mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
+                    checkAllFixedVolumeDevices();
+                    if (mHdmiManager != null) {
+                        synchronized (mHdmiManager) {
+                            if (mHdmiPlaybackClient != null) {
+                                mHdmiCecSink = false;
+                                mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback);
+                            }
+                        }
+                    }
+                }
+            } else {
+                if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
+                    if (mHdmiManager != null) {
+                        synchronized (mHdmiManager) {
+                            mHdmiCecSink = false;
+                        }
+                    }
+                }
             }
             if (!isUsb && (device != AudioSystem.DEVICE_IN_WIRED_HEADSET)) {
                 sendDeviceConnectionIntent(device, state, name);
@@ -4577,18 +4734,20 @@
                     if (cameraSoundForced != mCameraSoundForced) {
                         mCameraSoundForced = cameraSoundForced;
 
-                        VolumeStreamState s = mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED];
-                        if (cameraSoundForced) {
-                            s.setAllIndexesToMax();
-                            mRingerModeAffectedStreams &=
-                                    ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
-                        } else {
-                            s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM]);
-                            mRingerModeAffectedStreams |=
-                                    (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
+                        if (!isPlatformTelevision()) {
+                            VolumeStreamState s = mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED];
+                            if (cameraSoundForced) {
+                                s.setAllIndexesToMax();
+                                mRingerModeAffectedStreams &=
+                                        ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
+                            } else {
+                                s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM]);
+                                mRingerModeAffectedStreams |=
+                                        (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
+                            }
+                            // take new state into account for streams muted by ringer mode
+                            setRingerModeInt(getRingerMode(), false);
                         }
-                        // take new state into account for streams muted by ringer mode
-                        setRingerModeInt(getRingerMode(), false);
 
                         sendMsg(mAudioHandler,
                                 MSG_SET_FORCE_USE,
@@ -4804,27 +4963,57 @@
     // to HdmiControlService so that audio recevier can handle volume change.
     //==========================================================================================
 
+    private class MyDisplayStatusCallback implements HdmiPlaybackClient.DisplayStatusCallback {
+        public void onComplete(int status) {
+            if (mHdmiManager != null) {
+                synchronized (mHdmiManager) {
+                    mHdmiCecSink = (status != HdmiControlManager.POWER_STATUS_UNKNOWN);
+                    // Television devices without CEC service apply software volume on HDMI output
+                    if (isPlatformTelevision() && !mHdmiCecSink) {
+                        mFixedVolumeDevices &= ~AudioSystem.DEVICE_OUT_HDMI;
+                    }
+                    checkAllFixedVolumeDevices();
+                }
+            }
+        }
+    };
+
     // If HDMI-CEC system audio is supported
     private boolean mHdmiSystemAudioSupported = false;
     // Set only when device is tv.
     private HdmiTvClient mHdmiTvClient;
+    // true if the device has system feature PackageManager.FEATURE_TELEVISION.
+    // cached HdmiControlManager interface
+    private HdmiControlManager mHdmiManager;
+    // Set only when device is a set-top box.
+    private HdmiPlaybackClient mHdmiPlaybackClient;
+    // true if we are a set-top box, an HDMI sink is connected and it supports CEC.
+    private boolean mHdmiCecSink;
+
+    private MyDisplayStatusCallback mHdmiDisplayStatusCallback = new MyDisplayStatusCallback();
 
     @Override
     public int setHdmiSystemAudioSupported(boolean on) {
-        if (mHdmiTvClient == null) {
-            Log.w(TAG, "Only Hdmi-Cec enabled TV device supports system audio mode.");
-            return AudioSystem.DEVICE_NONE;
-        }
+        int device = AudioSystem.DEVICE_NONE;
+        if (mHdmiManager != null) {
+            synchronized (mHdmiManager) {
+                if (mHdmiTvClient == null) {
+                    Log.w(TAG, "Only Hdmi-Cec enabled TV device supports system audio mode.");
+                    return device;
+                }
 
-        synchronized (mHdmiTvClient) {
-            if (mHdmiSystemAudioSupported == on) {
-                return AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC);
+                synchronized (mHdmiTvClient) {
+                    if (mHdmiSystemAudioSupported != on) {
+                        mHdmiSystemAudioSupported = on;
+                        AudioSystem.setForceUse(AudioSystem.FOR_HDMI_SYSTEM_AUDIO,
+                                on ? AudioSystem.FORCE_HDMI_SYSTEM_AUDIO_ENFORCED :
+                                     AudioSystem.FORCE_NONE);
+                    }
+                    device = AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC);
+                }
             }
-            mHdmiSystemAudioSupported = on;
-            AudioSystem.setForceUse(AudioSystem.FOR_HDMI_SYSTEM_AUDIO,
-                    on ? AudioSystem.FORCE_HDMI_SYSTEM_AUDIO_ENFORCED : AudioSystem.FORCE_NONE);
         }
-        return AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC);
+        return device;
     }
 
     //==========================================================================================
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index f41c6dd..d5111f4 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -744,12 +744,12 @@
          * session.
          */
         void release() {
+            removeOverlayView(true);
             onRelease();
             if (mSurface != null) {
                 mSurface.release();
                 mSurface = null;
             }
-            removeOverlayView(true);
         }
 
         /**
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c0ac023..fed68f9 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -46,8 +46,8 @@
 import android.service.voice.IVoiceInteractionSession;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-
 import android.util.SparseIntArray;
+
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IAppOpsService;
@@ -222,7 +222,11 @@
 
 public final class ActivityManagerService extends ActivityManagerNative
         implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
+
     private static final String USER_DATA_DIR = "/data/user/";
+    // File that stores last updated system version and called preboot receivers
+    static final String CALLED_PRE_BOOTS_FILENAME = "called_pre_boots.dat";
+
     static final String TAG = "ActivityManager";
     static final String TAG_MU = "ActivityManagerServiceMU";
     static final boolean DEBUG = false;
@@ -354,6 +358,8 @@
     static final int ALLOW_NON_FULL_IN_PROFILE = 1;
     static final int ALLOW_FULL_ONLY = 2;
 
+    static final int LAST_PREBOOT_DELIVERED_FILE_VERSION = 10000;
+
     /** All system services */
     SystemServiceManager mSystemServiceManager;
 
@@ -9947,12 +9953,10 @@
     private static File getCalledPreBootReceiversFile() {
         File dataDir = Environment.getDataDirectory();
         File systemDir = new File(dataDir, "system");
-        File fname = new File(systemDir, "called_pre_boots.dat");
+        File fname = new File(systemDir, CALLED_PRE_BOOTS_FILENAME);
         return fname;
     }
 
-    static final int LAST_DONE_VERSION = 10000;
-
     private static ArrayList<ComponentName> readLastDonePreBootReceivers() {
         ArrayList<ComponentName> lastDoneReceivers = new ArrayList<ComponentName>();
         File file = getCalledPreBootReceiversFile();
@@ -9961,7 +9965,7 @@
             fis = new FileInputStream(file);
             DataInputStream dis = new DataInputStream(new BufferedInputStream(fis, 2048));
             int fvers = dis.readInt();
-            if (fvers == LAST_DONE_VERSION) {
+            if (fvers == LAST_PREBOOT_DELIVERED_FILE_VERSION) {
                 String vers = dis.readUTF();
                 String codename = dis.readUTF();
                 String build = dis.readUTF();
@@ -9990,16 +9994,15 @@
         }
         return lastDoneReceivers;
     }
-    
+
     private static void writeLastDonePreBootReceivers(ArrayList<ComponentName> list) {
         File file = getCalledPreBootReceiversFile();
         FileOutputStream fos = null;
         DataOutputStream dos = null;
         try {
-            Slog.i(TAG, "Writing new set of last done pre-boot receivers...");
             fos = new FileOutputStream(file);
             dos = new DataOutputStream(new BufferedOutputStream(fos, 2048));
-            dos.writeInt(LAST_DONE_VERSION);
+            dos.writeInt(LAST_PREBOOT_DELIVERED_FILE_VERSION);
             dos.writeUTF(android.os.Build.VERSION.RELEASE);
             dos.writeUTF(android.os.Build.VERSION.CODENAME);
             dos.writeUTF(android.os.Build.VERSION.INCREMENTAL);
@@ -10023,11 +10026,92 @@
             }
         }
     }
-    
+
+    private boolean deliverPreBootCompleted(final Runnable onFinishCallback,
+            ArrayList<ComponentName> doneReceivers, int userId) {
+        boolean waitingUpdate = false;
+        Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
+        List<ResolveInfo> ris = null;
+        try {
+            ris = AppGlobals.getPackageManager().queryIntentReceivers(
+                    intent, null, 0, userId);
+        } catch (RemoteException e) {
+        }
+        if (ris != null) {
+            for (int i=ris.size()-1; i>=0; i--) {
+                if ((ris.get(i).activityInfo.applicationInfo.flags
+                        &ApplicationInfo.FLAG_SYSTEM) == 0) {
+                    ris.remove(i);
+                }
+            }
+            intent.addFlags(Intent.FLAG_RECEIVER_BOOT_UPGRADE);
+
+            // For User 0, load the version number. When delivering to a new user, deliver
+            // to all receivers.
+            if (userId == UserHandle.USER_OWNER) {
+                ArrayList<ComponentName> lastDoneReceivers = readLastDonePreBootReceivers();
+                for (int i=0; i<ris.size(); i++) {
+                    ActivityInfo ai = ris.get(i).activityInfo;
+                    ComponentName comp = new ComponentName(ai.packageName, ai.name);
+                    if (lastDoneReceivers.contains(comp)) {
+                        // We already did the pre boot receiver for this app with the current
+                        // platform version, so don't do it again...
+                        ris.remove(i);
+                        i--;
+                        // ...however, do keep it as one that has been done, so we don't
+                        // forget about it when rewriting the file of last done receivers.
+                        doneReceivers.add(comp);
+                    }
+                }
+            }
+
+            // If primary user, send broadcast to all available users, else just to userId
+            final int[] users = userId == UserHandle.USER_OWNER ? getUsersLocked()
+                    : new int[] { userId };
+            for (int i = 0; i < ris.size(); i++) {
+                ActivityInfo ai = ris.get(i).activityInfo;
+                ComponentName comp = new ComponentName(ai.packageName, ai.name);
+                doneReceivers.add(comp);
+                intent.setComponent(comp);
+                for (int j=0; j<users.length; j++) {
+                    IIntentReceiver finisher = null;
+                    // On last receiver and user, set up a completion callback
+                    if (i == ris.size() - 1 && j == users.length - 1 && onFinishCallback != null) {
+                        finisher = new IIntentReceiver.Stub() {
+                            public void performReceive(Intent intent, int resultCode,
+                                    String data, Bundle extras, boolean ordered,
+                                    boolean sticky, int sendingUser) {
+                                // The raw IIntentReceiver interface is called
+                                // with the AM lock held, so redispatch to
+                                // execute our code without the lock.
+                                mHandler.post(onFinishCallback);
+                            }
+                        };
+                    }
+                    Slog.i(TAG, "Sending system update to " + intent.getComponent()
+                            + " for user " + users[j]);
+                    broadcastIntentLocked(null, null, intent, null, finisher,
+                            0, null, null, null, AppOpsManager.OP_NONE,
+                            true, false, MY_PID, Process.SYSTEM_UID,
+                            users[j]);
+                    if (finisher != null) {
+                        waitingUpdate = true;
+                    }
+                }
+            }
+        }
+
+        return waitingUpdate;
+    }
+
     public void systemReady(final Runnable goingCallback) {
         synchronized(this) {
             if (mSystemReady) {
-                if (goingCallback != null) goingCallback.run();
+                // If we're done calling all the receivers, run the next "boot phase" passed in
+                // by the SystemServer
+                if (goingCallback != null) {
+                    goingCallback.run();
+                }
                 return;
             }
 
@@ -10048,82 +10132,20 @@
                 if (mWaitingUpdate) {
                     return;
                 }
-                Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
-                List<ResolveInfo> ris = null;
-                try {
-                    ris = AppGlobals.getPackageManager().queryIntentReceivers(
-                            intent, null, 0, 0);
-                } catch (RemoteException e) {
-                }
-                if (ris != null) {
-                    for (int i=ris.size()-1; i>=0; i--) {
-                        if ((ris.get(i).activityInfo.applicationInfo.flags
-                                &ApplicationInfo.FLAG_SYSTEM) == 0) {
-                            ris.remove(i);
+                final ArrayList<ComponentName> doneReceivers = new ArrayList<ComponentName>();
+                mWaitingUpdate = deliverPreBootCompleted(new Runnable() {
+                    public void run() {
+                        synchronized (ActivityManagerService.this) {
+                            mDidUpdate = true;
                         }
+                        writeLastDonePreBootReceivers(doneReceivers);
+                        showBootMessage(mContext.getText(
+                                R.string.android_upgrading_complete),
+                                false);
+                        systemReady(goingCallback);
                     }
-                    intent.addFlags(Intent.FLAG_RECEIVER_BOOT_UPGRADE);
+                }, doneReceivers, UserHandle.USER_OWNER);
 
-                    ArrayList<ComponentName> lastDoneReceivers = readLastDonePreBootReceivers();
-
-                    final ArrayList<ComponentName> doneReceivers = new ArrayList<ComponentName>();
-                    for (int i=0; i<ris.size(); i++) {
-                        ActivityInfo ai = ris.get(i).activityInfo;
-                        ComponentName comp = new ComponentName(ai.packageName, ai.name);
-                        if (lastDoneReceivers.contains(comp)) {
-                            // We already did the pre boot receiver for this app with the current
-                            // platform version, so don't do it again...
-                            ris.remove(i);
-                            i--;
-                            // ...however, do keep it as one that has been done, so we don't
-                            // forget about it when rewriting the file of last done receivers.
-                            doneReceivers.add(comp);
-                        }
-                    }
-
-                    final int[] users = getUsersLocked();
-                    for (int i=0; i<ris.size(); i++) {
-                        ActivityInfo ai = ris.get(i).activityInfo;
-                        ComponentName comp = new ComponentName(ai.packageName, ai.name);
-                        doneReceivers.add(comp);
-                        intent.setComponent(comp);
-                        for (int j=0; j<users.length; j++) {
-                            IIntentReceiver finisher = null;
-                            if (i == ris.size()-1 && j == users.length-1) {
-                                finisher = new IIntentReceiver.Stub() {
-                                    public void performReceive(Intent intent, int resultCode,
-                                            String data, Bundle extras, boolean ordered,
-                                            boolean sticky, int sendingUser) {
-                                        // The raw IIntentReceiver interface is called
-                                        // with the AM lock held, so redispatch to
-                                        // execute our code without the lock.
-                                        mHandler.post(new Runnable() {
-                                            public void run() {
-                                                synchronized (ActivityManagerService.this) {
-                                                    mDidUpdate = true;
-                                                }
-                                                writeLastDonePreBootReceivers(doneReceivers);
-                                                showBootMessage(mContext.getText(
-                                                        R.string.android_upgrading_complete),
-                                                        false);
-                                                systemReady(goingCallback);
-                                            }
-                                        });
-                                    }
-                                };
-                            }
-                            Slog.i(TAG, "Sending system update to " + intent.getComponent()
-                                    + " for user " + users[j]);
-                            broadcastIntentLocked(null, null, intent, null, finisher,
-                                    0, null, null, null, AppOpsManager.OP_NONE,
-                                    true, false, MY_PID, Process.SYSTEM_UID,
-                                    users[j]);
-                            if (finisher != null) {
-                                mWaitingUpdate = true;
-                            }
-                        }
-                    }
-                }
                 if (mWaitingUpdate) {
                     return;
                 }
@@ -17156,6 +17178,7 @@
                 }
 
                 if (needStart) {
+                    // Send USER_STARTED broadcast
                     Intent intent = new Intent(Intent.ACTION_USER_STARTED);
                     intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                             | Intent.FLAG_RECEIVER_FOREGROUND);
@@ -17167,6 +17190,11 @@
 
                 if ((userInfo.flags&UserInfo.FLAG_INITIALIZED) == 0) {
                     if (userId != UserHandle.USER_OWNER) {
+                        // Send PRE_BOOT_COMPLETED broadcasts for this new user
+                        final ArrayList<ComponentName> doneReceivers
+                                = new ArrayList<ComponentName>();
+                        deliverPreBootCompleted(null, doneReceivers, userId);
+
                         Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE);
                         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
                         broadcastIntentLocked(null, null, intent, null,
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 013e9fe..fa8626f 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -94,7 +94,6 @@
 
     private final INetworkManagementService mNMService;
     private final INetworkStatsService mStatsService;
-    private final ConnectivityManager mConnManager;
     private Looper mLooper;
 
     private HashMap<String, TetherInterfaceSM> mIfaces; // all tethered/tetherable ifaces
@@ -135,7 +134,6 @@
         mContext = context;
         mNMService = nmService;
         mStatsService = statsService;
-        mConnManager = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
         mLooper = looper;
 
         mPublicSync = new Object();
@@ -175,6 +173,12 @@
         mDefaultDnsServers[1] = DNS_DEFAULT_SERVER2;
     }
 
+    // We can't do this once in the Tethering() constructor and cache the value, because the
+    // CONNECTIVITY_SERVICE is registered only after the Tethering() constructor has completed.
+    private ConnectivityManager getConnectivityManager() {
+        return (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+    }
+
     void updateConfiguration() {
         String[] tetherableUsbRegexs = mContext.getResources().getStringArray(
                 com.android.internal.R.array.config_tether_usb_regexs);
@@ -366,7 +370,7 @@
     // TODO - move all private methods used only by the state machine into the state machine
     // to clarify what needs synchronized protection.
     private void sendTetherStateChangedBroadcast() {
-        if (!mConnManager.isTetheringSupported()) return;
+        if (!getConnectivityManager().isTetheringSupported()) return;
 
         ArrayList<String> availableList = new ArrayList<String>();
         ArrayList<String> activeList = new ArrayList<String>();
@@ -1183,8 +1187,8 @@
                 int result = PhoneConstants.APN_REQUEST_FAILED;
                 String enableString = enableString(apnType);
                 if (enableString == null) return false;
-                result = mConnManager.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
-                        enableString);
+                result = getConnectivityManager().startUsingNetworkFeature(
+                        ConnectivityManager.TYPE_MOBILE, enableString);
                 switch (result) {
                 case PhoneConstants.APN_ALREADY_ACTIVE:
                 case PhoneConstants.APN_REQUEST_STARTED:
@@ -1205,8 +1209,8 @@
                 // ignore pending renewal requests
                 ++mCurrentConnectionSequence;
                 if (mMobileApnReserved != ConnectivityManager.TYPE_NONE) {
-                    mConnManager.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
-                            enableString(mMobileApnReserved));
+                    getConnectivityManager().stopUsingNetworkFeature(
+                            ConnectivityManager.TYPE_MOBILE, enableString(mMobileApnReserved));
                     mMobileApnReserved = ConnectivityManager.TYPE_NONE;
                 }
                 return true;
@@ -1269,7 +1273,8 @@
                     }
 
                     for (Integer netType : mUpstreamIfaceTypes) {
-                        NetworkInfo info = mConnManager.getNetworkInfo(netType.intValue());
+                        NetworkInfo info =
+                                getConnectivityManager().getNetworkInfo(netType.intValue());
                         if ((info != null) && info.isConnected()) {
                             upType = netType.intValue();
                             break;
@@ -1307,7 +1312,8 @@
                         sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
                     }
                 } else {
-                    LinkProperties linkProperties = mConnManager.getLinkProperties(upType);
+                    LinkProperties linkProperties =
+                            getConnectivityManager().getLinkProperties(upType);
                     if (linkProperties != null) {
                         // Find the interface with the default IPv4 route. It may be the
                         // interface described by linkProperties, or one of the interfaces
diff --git a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
index 397ef13..051ed0e 100644
--- a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
+++ b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
@@ -34,8 +34,10 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * This test is intended to measure the amount of memory applications use when
@@ -57,11 +59,12 @@
 
     private static final String TAG = "MemoryUsageInstrumentation";
     private static final String KEY_APPS = "apps";
-
+    private static final String KEY_PROCS = "persistent";
+    private static final String LAUNCHER_KEY = "launcher";
     private Map<String, Intent> mNameToIntent;
     private Map<String, String> mNameToProcess;
     private Map<String, String> mNameToResultKey;
-
+    private Set<String> mPersistentProcesses;
     private IActivityManager mAm;
 
     public void testMemory() {
@@ -75,35 +78,61 @@
 
         Bundle results = new Bundle();
         for (String app : mNameToResultKey.keySet()) {
-            String processName;
-            try {
-                processName = startApp(app);
-                measureMemory(app, processName, results);
-                closeApp();
-            } catch (NameNotFoundException e) {
-                Log.i(TAG, "Application " + app + " not found");
+            if (!mPersistentProcesses.contains(app)) {
+                String processName;
+                try {
+                    processName = startApp(app);
+                    measureMemory(app, processName, results);
+                    closeApp();
+                } catch (NameNotFoundException e) {
+                    Log.i(TAG, "Application " + app + " not found");
+                }
+            } else {
+                measureMemory(app, app, results);
             }
-
         }
         instrumentation.sendStatus(0, results);
     }
 
-    private void parseArgs(Bundle args) {
-        mNameToResultKey = new HashMap<String, String>();
-        String appList = args.getString(KEY_APPS);
+    private String getLauncherPackageName() {
+      Intent intent = new Intent(Intent.ACTION_MAIN);
+      intent.addCategory(Intent.CATEGORY_HOME);
+      ResolveInfo resolveInfo = getInstrumentation().getContext().
+          getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+      return resolveInfo.activityInfo.packageName;
+    }
 
-        if (appList == null)
-            return;
-
-        String appNames[] = appList.split("\\|");
-        for (String pair : appNames) {
+    private Map<String, String> parseListToMap(String list) {
+        Map<String, String> map = new HashMap<String, String>();
+        String names[] = list.split("\\|");
+        for (String pair : names) {
             String[] parts = pair.split("\\^");
             if (parts.length != 2) {
                 Log.e(TAG, "The apps key is incorectly formatted");
                 fail();
             }
+            map.put(parts[0], parts[1]);
+        }
+        return map;
+    }
 
-            mNameToResultKey.put(parts[0], parts[1]);
+    private void parseArgs(Bundle args) {
+        mNameToResultKey = new HashMap<String, String>();
+        mPersistentProcesses = new HashSet<String>();
+        String appList = args.getString(KEY_APPS);
+        String procList = args.getString(KEY_PROCS);
+        String mLauncherPackageName = getLauncherPackageName();
+        mPersistentProcesses.add(mLauncherPackageName);
+        mNameToResultKey.put(mLauncherPackageName, LAUNCHER_KEY);
+        if (appList == null && procList == null)
+            return;
+        if (appList != null) {
+            mNameToResultKey.putAll(parseListToMap(appList));
+        }
+        if (procList != null) {
+            Map<String, String> procMap = parseListToMap(procList);
+            mPersistentProcesses.addAll(procMap.keySet());
+            mNameToResultKey.putAll(procMap);
         }
     }
 
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index b482697..94e9a5d 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -357,6 +357,7 @@
      * @hide
      * Uid of last app modifying the configuration
      */
+    @SystemApi
     public int lastUpdateUid;
 
     /**