Merge "Support for changing density of DrawableWrapper subclasses"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 093ea80..d7dd3ec 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5018,6 +5018,12 @@
         <attr name="autoMirrored" format="boolean" />
     </declare-styleable>
 
+    <!-- Drawable class used to wrap other drawables. -->
+    <declare-styleable name="DrawableWrapper">
+        <!-- The wrapped drawable. -->
+        <attr name="drawable" />
+    </declare-styleable>
+
     <!-- Drawable used to render several states. Each state is represented by
          a child drawable. -->
     <declare-styleable name="StateListDrawable">
@@ -5385,6 +5391,7 @@
         <attr name="color" />
     </declare-styleable>
 
+    <!-- Drawable used to wrap and inset another drawable. -->
     <declare-styleable name="InsetDrawable">
         <attr name="visible" />
         <attr name="drawable" />
diff --git a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
index 4fc5ede..971a3a2 100644
--- a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
@@ -50,7 +50,7 @@
      * Creates a new animated rotating drawable with no wrapped drawable.
      */
     public AnimatedRotateDrawable() {
-        this(new AnimatedRotateState(null), null);
+        this(new AnimatedRotateState(null, null), null);
     }
 
     @Override
@@ -126,58 +126,22 @@
             @NonNull AttributeSet attrs, @Nullable Theme theme)
             throws XmlPullParserException, IOException {
         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimatedRotateDrawable);
-        super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedRotateDrawable_visible);
+
+        // Inflation will advance the XmlPullParser and AttributeSet.
+        super.inflate(r, parser, attrs, theme);
 
         updateStateFromTypedArray(a);
-        inflateChildDrawable(r, parser, attrs, theme);
         verifyRequiredAttributes(a);
         a.recycle();
 
         updateLocalState();
     }
 
-    private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
-        // If we're not waiting on a theme, verify required attributes.
-        if (getDrawable() == null && (mState.mThemeAttrs == null
-                || mState.mThemeAttrs[R.styleable.AnimatedRotateDrawable_drawable] == 0)) {
-            throw new XmlPullParserException(a.getPositionDescription()
-                    + ": <animated-rotate> tag requires a 'drawable' attribute or "
-                    + "child tag defining a drawable");
-        }
-    }
-
     @Override
-    void updateStateFromTypedArray(TypedArray a) {
-        super.updateStateFromTypedArray(a);
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
 
         final AnimatedRotateState state = mState;
-
-        if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotX)) {
-            final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotX);
-            state.mPivotXRel = tv.type == TypedValue.TYPE_FRACTION;
-            state.mPivotX = state.mPivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
-        }
-
-        if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotY)) {
-            final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotY);
-            state.mPivotYRel = tv.type == TypedValue.TYPE_FRACTION;
-            state.mPivotY = state.mPivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
-        }
-
-        setFramesCount(a.getInt(
-                R.styleable.AnimatedRotateDrawable_framesCount, state.mFramesCount));
-        setFramesDuration(a.getInt(
-                R.styleable.AnimatedRotateDrawable_frameDuration, state.mFrameDuration));
-
-        final Drawable dr = a.getDrawable(R.styleable.AnimatedRotateDrawable_drawable);
-        if (dr != null) {
-            setDrawable(dr);
-        }
-    }
-
-    @Override
-    public void applyTheme(@Nullable Theme t) {
-        final AnimatedRotateState state = mState;
         if (state == null) {
             return;
         }
@@ -195,13 +159,49 @@
             }
         }
 
-        // The drawable may have changed as a result of applying the theme, so
-        // apply the theme to the wrapped drawable last.
-        super.applyTheme(t);
-
         updateLocalState();
     }
 
+    private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
+        // If we're not waiting on a theme, verify required attributes.
+        if (getDrawable() == null && (mState.mThemeAttrs == null
+                || mState.mThemeAttrs[R.styleable.AnimatedRotateDrawable_drawable] == 0)) {
+            throw new XmlPullParserException(a.getPositionDescription()
+                    + ": <animated-rotate> tag requires a 'drawable' attribute or "
+                    + "child tag defining a drawable");
+        }
+    }
+
+    private void updateStateFromTypedArray(@NonNull TypedArray a) {
+        final AnimatedRotateState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
+        if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotX)) {
+            final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotX);
+            state.mPivotXRel = tv.type == TypedValue.TYPE_FRACTION;
+            state.mPivotX = state.mPivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
+        }
+
+        if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotY)) {
+            final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotY);
+            state.mPivotYRel = tv.type == TypedValue.TYPE_FRACTION;
+            state.mPivotY = state.mPivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
+        }
+
+        setFramesCount(a.getInt(
+                R.styleable.AnimatedRotateDrawable_framesCount, state.mFramesCount));
+        setFramesDuration(a.getInt(
+                R.styleable.AnimatedRotateDrawable_frameDuration, state.mFrameDuration));
+    }
+
     public void setFramesCount(int framesCount) {
         mState.mFramesCount = framesCount;
         mIncrement = 360.0f / mState.mFramesCount;
@@ -211,7 +211,15 @@
         mState.mFrameDuration = framesDuration;
     }
 
+    @Override
+    DrawableWrapperState mutateConstantState() {
+        mState = new AnimatedRotateState(mState, null);
+        return mState;
+    }
+
     static final class AnimatedRotateState extends DrawableWrapper.DrawableWrapperState {
+        private int[] mThemeAttrs;
+
         boolean mPivotXRel = false;
         float mPivotX = 0;
         boolean mPivotYRel = false;
@@ -219,8 +227,8 @@
         int mFrameDuration = 150;
         int mFramesCount = 12;
 
-        public AnimatedRotateState(AnimatedRotateState orig) {
-            super(orig);
+        public AnimatedRotateState(AnimatedRotateState orig, Resources res) {
+            super(orig, res);
 
             if (orig != null) {
                 mPivotXRel = orig.mPivotXRel;
diff --git a/graphics/java/android/graphics/drawable/ClipDrawable.java b/graphics/java/android/graphics/drawable/ClipDrawable.java
index 31fccd0..cdd336d 100644
--- a/graphics/java/android/graphics/drawable/ClipDrawable.java
+++ b/graphics/java/android/graphics/drawable/ClipDrawable.java
@@ -21,6 +21,8 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.Resources.Theme;
@@ -59,7 +61,7 @@
     private ClipState mState;
 
     ClipDrawable() {
-        this(new ClipState(null), null);
+        this(new ClipState(null, null), null);
     }
 
     /**
@@ -72,7 +74,7 @@
      *                   {@link #VERTICAL}
      */
     public ClipDrawable(Drawable drawable, int gravity, int orientation) {
-        this(new ClipState(null), null);
+        this(new ClipState(null, null), null);
 
         mState.mGravity = gravity;
         mState.mOrientation = orientation;
@@ -81,46 +83,24 @@
     }
 
     @Override
-    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
             throws XmlPullParserException, IOException {
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ClipDrawable);
+
+        // Inflation will advance the XmlPullParser and AttributeSet.
         super.inflate(r, parser, attrs, theme);
 
-        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ClipDrawable);
         updateStateFromTypedArray(a);
-        inflateChildDrawable(r, parser, attrs, theme);
         verifyRequiredAttributes(a);
         a.recycle();
     }
 
-    private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
-        // If we're not waiting on a theme, verify required attributes.
-        if (getDrawable() == null && (mState.mThemeAttrs == null
-                || mState.mThemeAttrs[R.styleable.ClipDrawable_drawable] == 0)) {
-            throw new XmlPullParserException(a.getPositionDescription()
-                    + ": <clip> tag requires a 'drawable' attribute or "
-                    + "child tag defining a drawable");
-        }
-    }
-
     @Override
-    void updateStateFromTypedArray(TypedArray a) {
-        super.updateStateFromTypedArray(a);
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
 
         final ClipState state = mState;
-        state.mOrientation = a.getInt(
-                R.styleable.ClipDrawable_clipOrientation, state.mOrientation);
-        state.mGravity = a.getInt(
-                R.styleable.ClipDrawable_gravity, state.mGravity);
-
-        final Drawable dr = a.getDrawable(R.styleable.ClipDrawable_drawable);
-        if (dr != null) {
-            setDrawable(dr);
-        }
-    }
-
-    @Override
-    public void applyTheme(Theme t) {
-        final ClipState state = mState;
         if (state == null) {
             return;
         }
@@ -136,10 +116,34 @@
                 a.recycle();
             }
         }
+    }
 
-        // The drawable may have changed as a result of applying the theme, so
-        // apply the theme to the wrapped drawable last.
-        super.applyTheme(t);
+    private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
+        // If we're not waiting on a theme, verify required attributes.
+        if (getDrawable() == null && (mState.mThemeAttrs == null
+                || mState.mThemeAttrs[R.styleable.ClipDrawable_drawable] == 0)) {
+            throw new XmlPullParserException(a.getPositionDescription()
+                    + ": <clip> tag requires a 'drawable' attribute or "
+                    + "child tag defining a drawable");
+        }
+    }
+
+    private void updateStateFromTypedArray(@NonNull TypedArray a) {
+        final ClipState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
+        state.mOrientation = a.getInt(
+                R.styleable.ClipDrawable_clipOrientation, state.mOrientation);
+        state.mGravity = a.getInt(
+                R.styleable.ClipDrawable_gravity, state.mGravity);
     }
 
     @Override
@@ -200,12 +204,20 @@
         }
     }
 
+    @Override
+    DrawableWrapperState mutateConstantState() {
+        mState = new ClipState(mState, null);
+        return mState;
+    }
+
     static final class ClipState extends DrawableWrapper.DrawableWrapperState {
+        private int[] mThemeAttrs;
+
         int mOrientation = HORIZONTAL;
         int mGravity = Gravity.LEFT;
 
-        ClipState(ClipState orig) {
-            super(orig);
+        ClipState(ClipState orig, Resources res) {
+            super(orig, res);
 
             if (orig != null) {
                 mOrientation = orig.mOrientation;
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index b95c183..ff28777 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -55,6 +55,8 @@
 import java.util.Arrays;
 import java.util.Collection;
 
+import com.android.internal.R;
+
 /**
  * A Drawable is a general abstraction for "something that can be drawn."  Most
  * often you will deal with Drawable as the type of resource retrieved for
@@ -791,8 +793,10 @@
 
     /**
      * Applies the specified theme to this Drawable and its children.
+     *
+     * @param t the theme to apply
      */
-    public void applyTheme(@SuppressWarnings("unused") Theme t) {
+    public void applyTheme(@NonNull @SuppressWarnings("unused") Theme t) {
     }
 
     public boolean canApplyTheme() {
@@ -1177,8 +1181,8 @@
      *
      * @see #inflate(Resources, XmlPullParser, AttributeSet, Theme)
      */
-    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
-            throws XmlPullParserException, IOException {
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs) throws XmlPullParserException, IOException {
         inflate(r, parser, attrs, null);
     }
 
@@ -1192,17 +1196,11 @@
      * @throws XmlPullParserException
      * @throws IOException
      */
-    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
             throws XmlPullParserException, IOException {
-        final TypedArray a;
-        if (theme != null) {
-            a = theme.obtainStyledAttributes(
-                    attrs, com.android.internal.R.styleable.Drawable, 0, 0);
-        } else {
-            a = r.obtainAttributes(attrs, com.android.internal.R.styleable.Drawable);
-        }
-
-        inflateWithAttributes(r, parser, a, com.android.internal.R.styleable.Drawable_visible);
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.Drawable);
+        mVisible = a.getBoolean(R.styleable.Drawable_visible, mVisible);
         a.recycle();
     }
 
@@ -1212,8 +1210,8 @@
      * @throws XmlPullParserException
      * @throws IOException
      */
-    void inflateWithAttributes(Resources r, XmlPullParser parser, TypedArray attrs, int visibleAttr)
-            throws XmlPullParserException, IOException {
+    void inflateWithAttributes(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull TypedArray attrs, int visibleAttr) throws XmlPullParserException, IOException {
         mVisible = attrs.getBoolean(visibleAttr, mVisible);
     }
 
diff --git a/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java
index 9185e1a..c427870 100644
--- a/graphics/java/android/graphics/drawable/DrawableWrapper.java
+++ b/graphics/java/android/graphics/drawable/DrawableWrapper.java
@@ -16,6 +16,8 @@
 
 package android.graphics.drawable;
 
+import com.android.internal.R;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -23,6 +25,7 @@
 import android.annotation.Nullable;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
+import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -33,6 +36,7 @@
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.util.DisplayMetrics;
 import android.view.View;
 
 import java.io.IOException;
@@ -112,7 +116,66 @@
         return mDrawable;
     }
 
-    void updateStateFromTypedArray(TypedArray a) {
+    @Override
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        super.inflate(r, parser, attrs, theme);
+
+        final DrawableWrapperState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        // The density may have changed since the last update. This will
+        // apply scaling to any existing constant state properties.
+        final int densityDpi = r.getDisplayMetrics().densityDpi;
+        final int targetDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+        state.setDensity(targetDensity);
+
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.DrawableWrapper);
+        updateStateFromTypedArray(a);
+        a.recycle();
+
+        inflateChildDrawable(r, parser, attrs, theme);
+    }
+
+    @Override
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
+
+        // If we load the drawable later as part of updating from the typed
+        // array, it will already be themed correctly. So, we can theme the
+        // local drawable first.
+        if (mDrawable != null && mDrawable.canApplyTheme()) {
+            mDrawable.applyTheme(t);
+        }
+
+        final DrawableWrapperState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        final int densityDpi = t.getResources().getDisplayMetrics().densityDpi;
+        final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+        state.setDensity(density);
+
+        if (state.mThemeAttrs != null) {
+            final TypedArray a = t.resolveAttributes(
+                    state.mThemeAttrs, R.styleable.DrawableWrapper);
+            updateStateFromTypedArray(a);
+            a.recycle();
+        }
+    }
+
+    /**
+     * Updates constant state properties from the provided typed array.
+     * <p>
+     * Implementing subclasses should call through to the super method first.
+     *
+     * @param a the typed array rom which properties should be read
+     */
+    private void updateStateFromTypedArray(@NonNull TypedArray a) {
         final DrawableWrapperState state = mState;
         if (state == null) {
             return;
@@ -124,20 +187,8 @@
         // Extract the theme attributes, if any.
         state.mThemeAttrs = a.extractThemeAttrs();
 
-        // TODO: Consider using R.styleable.DrawableWrapper_drawable
-    }
-
-    @Override
-    public void applyTheme(Resources.Theme t) {
-        super.applyTheme(t);
-
-        final DrawableWrapperState state = mState;
-        if (state == null) {
-            return;
-        }
-
-        if (mDrawable != null && mDrawable.canApplyTheme()) {
-            mDrawable.applyTheme(t);
+        if (a.hasValueOrEmpty(R.styleable.DrawableWrapper_drawable)) {
+            setDrawable(a.getDrawable(R.styleable.DrawableWrapper_drawable));
         }
     }
 
@@ -371,8 +422,9 @@
      * child element will take precedence over any other child elements or
      * explicit drawable attribute.
      */
-    void inflateChildDrawable(Resources r, XmlPullParser parser, AttributeSet attrs,
-            Resources.Theme theme) throws XmlPullParserException, IOException {
+    private void inflateChildDrawable(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
         // Seek to the first child element.
         Drawable dr = null;
         int type;
@@ -390,17 +442,61 @@
     }
 
     abstract static class DrawableWrapperState extends Drawable.ConstantState {
-        int[] mThemeAttrs;
+        private int[] mThemeAttrs;
+
         int mChangingConfigurations;
+        int mDensity = DisplayMetrics.DENSITY_DEFAULT;
 
         Drawable.ConstantState mDrawableState;
 
-        DrawableWrapperState(DrawableWrapperState orig) {
+        DrawableWrapperState(@Nullable DrawableWrapperState orig, @Nullable Resources res) {
             if (orig != null) {
                 mThemeAttrs = orig.mThemeAttrs;
                 mChangingConfigurations = orig.mChangingConfigurations;
                 mDrawableState = orig.mDrawableState;
             }
+
+            final int density;
+            if (res != null) {
+                density = res.getDisplayMetrics().densityDpi;
+            } else if (orig != null) {
+                density = orig.mDensity;
+            } else {
+                density = 0;
+            }
+
+            mDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density;
+        }
+
+        /**
+         * Sets the constant state density.
+         * <p>
+         * If the density has been previously set, dispatches the change to
+         * subclasses so that density-dependent properties may be scaled as
+         * necessary.
+         *
+         * @param targetDensity the new constant state density
+         */
+        public final void setDensity(int targetDensity) {
+            if (mDensity != targetDensity) {
+                final int sourceDensity = mDensity;
+                mDensity = targetDensity;
+
+                onDensityChanged(sourceDensity, targetDensity);
+            }
+        }
+
+        /**
+         * Called when the constant state density changes.
+         * <p>
+         * Subclasses with density-dependent constant state properties should
+         * override this method and scale their properties as necessary.
+         *
+         * @param sourceDensity the previous constant state density
+         * @param targetDensity the new constant state density
+         */
+        void onDensityChanged(int sourceDensity, int targetDensity) {
+            // Stub method.
         }
 
         @Override
@@ -425,7 +521,7 @@
         }
 
         @Override
-        public abstract Drawable newDrawable(Resources res);
+        public abstract Drawable newDrawable(@Nullable Resources res);
 
         @Override
         public int getChangingConfigurations() {
diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java
index e1ebdbb..927b9c9 100644
--- a/graphics/java/android/graphics/drawable/InsetDrawable.java
+++ b/graphics/java/android/graphics/drawable/InsetDrawable.java
@@ -22,14 +22,17 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
+import android.graphics.Bitmap;
 import android.graphics.Insets;
 import android.graphics.Outline;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.util.DisplayMetrics;
 
 import java.io.IOException;
 
@@ -58,7 +61,7 @@
      * No-arg constructor used by drawable inflation.
      */
     InsetDrawable() {
-        this(new InsetState(null), null);
+        this(new InsetState(null, null), null);
     }
 
     /**
@@ -67,7 +70,7 @@
      * @param drawable The drawable to inset.
      * @param inset Inset in pixels around the drawable.
      */
-    public InsetDrawable(Drawable drawable, int inset) {
+    public InsetDrawable(@Nullable Drawable drawable, int inset) {
         this(drawable, inset, inset, inset, inset);
     }
 
@@ -80,9 +83,9 @@
      * @param insetRight Right inset in pixels.
      * @param insetBottom Bottom inset in pixels.
      */
-    public InsetDrawable(Drawable drawable, int insetLeft, int insetTop,int insetRight,
-            int insetBottom) {
-        this(new InsetState(null), null);
+    public InsetDrawable(@Nullable Drawable drawable, int insetLeft, int insetTop,
+            int insetRight, int insetBottom) {
+        this(new InsetState(null, null), null);
 
         mState.mInsetLeft = insetLeft;
         mState.mInsetTop = insetTop;
@@ -93,70 +96,24 @@
     }
 
     @Override
-    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
             throws XmlPullParserException, IOException {
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.InsetDrawable);
+
+        // Inflation will advance the XmlPullParser and AttributeSet.
         super.inflate(r, parser, attrs, theme);
 
-        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.InsetDrawable);
         updateStateFromTypedArray(a);
-        inflateChildDrawable(r, parser, attrs, theme);
         verifyRequiredAttributes(a);
         a.recycle();
     }
 
-    private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
-        // If we're not waiting on a theme, verify required attributes.
-        if (getDrawable() == null && (mState.mThemeAttrs == null
-                || mState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) {
-            throw new XmlPullParserException(a.getPositionDescription()
-                    + ": <inset> tag requires a 'drawable' attribute or "
-                    + "child tag defining a drawable");
-        }
-    }
-
     @Override
-    void updateStateFromTypedArray(TypedArray a) {
-        super.updateStateFromTypedArray(a);
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
 
         final InsetState state = mState;
-        final int N = a.getIndexCount();
-        for (int i = 0; i < N; i++) {
-            final int attr = a.getIndex(i);
-            switch (attr) {
-                case R.styleable.InsetDrawable_drawable:
-                    final Drawable dr = a.getDrawable(attr);
-                    if (dr != null) {
-                        setDrawable(dr);
-                    }
-                    break;
-                case R.styleable.InsetDrawable_inset:
-                    final int inset = a.getDimensionPixelOffset(attr, Integer.MIN_VALUE);
-                    if (inset != Integer.MIN_VALUE) {
-                        state.mInsetLeft = inset;
-                        state.mInsetTop = inset;
-                        state.mInsetRight = inset;
-                        state.mInsetBottom = inset;
-                    }
-                    break;
-                case R.styleable.InsetDrawable_insetLeft:
-                    state.mInsetLeft = a.getDimensionPixelOffset(attr, state.mInsetLeft);
-                    break;
-                case R.styleable.InsetDrawable_insetTop:
-                    state.mInsetTop = a.getDimensionPixelOffset(attr, state.mInsetTop);
-                    break;
-                case R.styleable.InsetDrawable_insetRight:
-                    state.mInsetRight = a.getDimensionPixelOffset(attr, state.mInsetRight);
-                    break;
-                case R.styleable.InsetDrawable_insetBottom:
-                    state.mInsetBottom = a.getDimensionPixelOffset(attr, state.mInsetBottom);
-                    break;
-            }
-        }
-    }
-
-    @Override
-    public void applyTheme(Theme t) {
-        final InsetState state = mState;
         if (state == null) {
             return;
         }
@@ -172,10 +129,47 @@
                 a.recycle();
             }
         }
+    }
 
-        // The drawable may have changed as a result of applying the theme, so
-        // apply the theme to the wrapped drawable last.
-        super.applyTheme(t);
+    private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
+        // If we're not waiting on a theme, verify required attributes.
+        if (getDrawable() == null && (mState.mThemeAttrs == null
+                || mState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) {
+            throw new XmlPullParserException(a.getPositionDescription()
+                    + ": <inset> tag requires a 'drawable' attribute or "
+                    + "child tag defining a drawable");
+        }
+    }
+
+    private void updateStateFromTypedArray(@NonNull TypedArray a) {
+        final InsetState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
+        // Inset attribute may be overridden by more specific attributes.
+        if (a.hasValue(R.styleable.InsetDrawable_inset)) {
+            final int inset = a.getDimensionPixelOffset(R.styleable.InsetDrawable_inset, 0);
+            state.mInsetLeft = inset;
+            state.mInsetTop = inset;
+            state.mInsetRight = inset;
+            state.mInsetBottom = inset;
+        }
+
+        state.mInsetLeft = a.getDimensionPixelOffset(
+                R.styleable.InsetDrawable_insetLeft, state.mInsetLeft);
+        state.mInsetRight = a.getDimensionPixelOffset(
+                R.styleable.InsetDrawable_insetRight, state.mInsetRight);
+        state.mInsetTop = a.getDimensionPixelOffset(
+                R.styleable.InsetDrawable_insetTop, state.mInsetTop);
+        state.mInsetBottom = a.getDimensionPixelOffset(
+                R.styleable.InsetDrawable_insetBottom, state.mInsetBottom);
     }
 
     @Override
@@ -243,30 +237,72 @@
 
     @Override
     DrawableWrapperState mutateConstantState() {
-        mState = new InsetState(mState);
+        mState = new InsetState(mState, null);
         return mState;
     }
 
     static final class InsetState extends DrawableWrapper.DrawableWrapperState {
+        private int[] mThemeAttrs;
+
         int mInsetLeft = 0;
         int mInsetTop = 0;
         int mInsetRight = 0;
         int mInsetBottom = 0;
 
-        InsetState(InsetState orig) {
-            super(orig);
+        InsetState(@Nullable InsetState orig, @Nullable Resources res) {
+            super(orig, res);
 
             if (orig != null) {
                 mInsetLeft = orig.mInsetLeft;
                 mInsetTop = orig.mInsetTop;
                 mInsetRight = orig.mInsetRight;
                 mInsetBottom = orig.mInsetBottom;
+
+                if (orig.mDensity != mDensity) {
+                    applyDensityScaling(orig.mDensity, mDensity);
+                }
             }
         }
 
         @Override
-        public Drawable newDrawable(Resources res) {
-            return new InsetDrawable(this, res);
+        void onDensityChanged(int sourceDensity, int targetDensity) {
+            super.onDensityChanged(sourceDensity, targetDensity);
+
+            applyDensityScaling(sourceDensity, targetDensity);
+        }
+
+        /**
+         * Called when the constant state density changes to scale
+         * density-dependent properties specific to insets.
+         *
+         * @param sourceDensity the previous constant state density
+         * @param targetDensity the new constant state density
+         */
+        private void applyDensityScaling(int sourceDensity, int targetDensity) {
+            mInsetLeft = Bitmap.scaleFromDensity(mInsetLeft, sourceDensity, targetDensity);
+            mInsetTop = Bitmap.scaleFromDensity(mInsetTop, sourceDensity, targetDensity);
+            mInsetRight = Bitmap.scaleFromDensity(mInsetRight, sourceDensity, targetDensity);
+            mInsetBottom = Bitmap.scaleFromDensity(mInsetBottom, sourceDensity, targetDensity);
+        }
+
+        @Override
+        public Drawable newDrawable(@Nullable Resources res) {
+            // If this drawable is being created for a different density,
+            // just create a new constant state and call it a day.
+            final InsetState state;
+            if (res != null) {
+                final int densityDpi = res.getDisplayMetrics().densityDpi;
+                final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+                if (density != mDensity) {
+                    state = new InsetState(this, res);
+                } else {
+                    state = this;
+                }
+            } else {
+                state = this;
+            }
+
+            return new InsetDrawable(state, res);
         }
     }
 
@@ -274,7 +310,7 @@
      * The one constructor to rule them all. This is called by all public
      * constructors to set the state and initialize local properties.
      */
-    private InsetDrawable(InsetState state, Resources res) {
+    private InsetDrawable(@NonNull InsetState state, @Nullable Resources res) {
         super(state, res);
 
         mState = state;
diff --git a/graphics/java/android/graphics/drawable/RotateDrawable.java b/graphics/java/android/graphics/drawable/RotateDrawable.java
index 036a078..1531ba2 100644
--- a/graphics/java/android/graphics/drawable/RotateDrawable.java
+++ b/graphics/java/android/graphics/drawable/RotateDrawable.java
@@ -21,6 +21,8 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.content.res.Resources;
@@ -58,22 +60,46 @@
      * Creates a new rotating drawable with no wrapped drawable.
      */
     public RotateDrawable() {
-        this(new RotateState(null), null);
+        this(new RotateState(null, null), null);
     }
 
     @Override
-    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
             throws XmlPullParserException, IOException {
         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.RotateDrawable);
-        super.inflateWithAttributes(r, parser, a, R.styleable.RotateDrawable_visible);
+
+        // Inflation will advance the XmlPullParser and AttributeSet.
+        super.inflate(r, parser, attrs, theme);
 
         updateStateFromTypedArray(a);
-        inflateChildDrawable(r, parser, attrs, theme);
         verifyRequiredAttributes(a);
         a.recycle();
     }
 
-    private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
+    @Override
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
+
+        final RotateState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        if (state.mThemeAttrs != null) {
+            final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.RotateDrawable);
+            try {
+                updateStateFromTypedArray(a);
+                verifyRequiredAttributes(a);
+            } catch (XmlPullParserException e) {
+                throw new RuntimeException(e);
+            } finally {
+                a.recycle();
+            }
+        }
+    }
+
+    private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
         // If we're not waiting on a theme, verify required attributes.
         if (getDrawable() == null && (mState.mThemeAttrs == null
                 || mState.mThemeAttrs[R.styleable.RotateDrawable_drawable] == 0)) {
@@ -83,11 +109,14 @@
         }
     }
 
-    @Override
-    void updateStateFromTypedArray(TypedArray a) {
-        super.updateStateFromTypedArray(a);
-
+    private void updateStateFromTypedArray(@NonNull TypedArray a) {
         final RotateState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
 
         // Extract the theme attributes, if any.
         state.mThemeAttrs = a.extractThemeAttrs();
@@ -109,35 +138,6 @@
         state.mToDegrees = a.getFloat(
                 R.styleable.RotateDrawable_toDegrees, state.mToDegrees);
         state.mCurrentDegrees = state.mFromDegrees;
-
-        final Drawable dr = a.getDrawable(R.styleable.RotateDrawable_drawable);
-        if (dr != null) {
-            setDrawable(dr);
-        }
-    }
-
-    @Override
-    public void applyTheme(Theme t) {
-        final RotateState state = mState;
-        if (state == null) {
-            return;
-        }
-
-        if (state.mThemeAttrs != null) {
-            final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.RotateDrawable);
-            try {
-                updateStateFromTypedArray(a);
-                verifyRequiredAttributes(a);
-            } catch (XmlPullParserException e) {
-                throw new RuntimeException(e);
-            } finally {
-                a.recycle();
-            }
-        }
-
-        // The drawable may have changed as a result of applying the theme, so
-        // apply the theme to the wrapped drawable last.
-        super.applyTheme(t);
     }
 
     @Override
@@ -316,11 +316,13 @@
 
     @Override
     DrawableWrapperState mutateConstantState() {
-        mState = new RotateState(mState);
+        mState = new RotateState(mState, null);
         return mState;
     }
 
     static final class RotateState extends DrawableWrapper.DrawableWrapperState {
+        private int[] mThemeAttrs;
+
         boolean mPivotXRel = true;
         float mPivotX = 0.5f;
         boolean mPivotYRel = true;
@@ -329,8 +331,8 @@
         float mToDegrees = 360.0f;
         float mCurrentDegrees = 0.0f;
 
-        RotateState(RotateState orig) {
-            super(orig);
+        RotateState(RotateState orig, Resources res) {
+            super(orig, res);
 
             if (orig != null) {
                 mPivotXRel = orig.mPivotXRel;
diff --git a/graphics/java/android/graphics/drawable/ScaleDrawable.java b/graphics/java/android/graphics/drawable/ScaleDrawable.java
index f9206b7..f87c19a 100644
--- a/graphics/java/android/graphics/drawable/ScaleDrawable.java
+++ b/graphics/java/android/graphics/drawable/ScaleDrawable.java
@@ -21,6 +21,8 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
@@ -67,7 +69,7 @@
     private ScaleState mState;
 
     ScaleDrawable() {
-        this(new ScaleState(null), null);
+        this(new ScaleState(null, null), null);
     }
 
     /**
@@ -83,7 +85,7 @@
      *                    is at the maximum value, or -1 to not scale height
      */
     public ScaleDrawable(Drawable drawable, int gravity, float scaleWidth, float scaleHeight) {
-        this(new ScaleState(null), null);
+        this(new ScaleState(null, null), null);
 
         mState.mGravity = gravity;
         mState.mScaleWidth = scaleWidth;
@@ -93,20 +95,46 @@
     }
 
     @Override
-    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
             throws XmlPullParserException, IOException {
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ScaleDrawable);
+
+        // Inflation will advance the XmlPullParser and AttributeSet.
         super.inflate(r, parser, attrs, theme);
 
-        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ScaleDrawable);
         updateStateFromTypedArray(a);
-        inflateChildDrawable(r, parser, attrs, theme);
         verifyRequiredAttributes(a);
         a.recycle();
 
         updateLocalState();
     }
 
-    private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
+    @Override
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
+
+        final ScaleState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        if (state.mThemeAttrs != null) {
+            final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ScaleDrawable);
+            try {
+                updateStateFromTypedArray(a);
+                verifyRequiredAttributes(a);
+            } catch (XmlPullParserException e) {
+                throw new RuntimeException(e);
+            } finally {
+                a.recycle();
+            }
+        }
+
+        updateLocalState();
+    }
+
+    private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
         // If we're not waiting on a theme, verify required attributes.
         if (getDrawable() == null && (mState.mThemeAttrs == null
                 || mState.mThemeAttrs[R.styleable.ScaleDrawable_drawable] == 0)) {
@@ -116,11 +144,18 @@
         }
     }
 
-    @Override
-    void updateStateFromTypedArray(TypedArray a) {
-        super.updateStateFromTypedArray(a);
-
+    private void updateStateFromTypedArray(@NonNull TypedArray a) {
         final ScaleState state = mState;
+        if (state == null) {
+            return;
+        }
+
+        // Account for any configuration changes.
+        state.mChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        state.mThemeAttrs = a.extractThemeAttrs();
+
         state.mScaleWidth = getPercent(a,
                 R.styleable.ScaleDrawable_scaleWidth, state.mScaleWidth);
         state.mScaleHeight = getPercent(a,
@@ -131,11 +166,6 @@
                 R.styleable.ScaleDrawable_useIntrinsicSizeAsMinimum, state.mUseIntrinsicSizeAsMin);
         state.mInitialLevel = a.getInt(
                 R.styleable.ScaleDrawable_level, state.mInitialLevel);
-
-        final Drawable dr = a.getDrawable(R.styleable.ScaleDrawable_drawable);
-        if (dr != null) {
-            setDrawable(dr);
-        }
     }
 
     private static float getPercent(TypedArray a, int index, float defaultValue) {
@@ -157,33 +187,6 @@
     }
 
     @Override
-    public void applyTheme(Theme t) {
-        final ScaleState state = mState;
-        if (state == null) {
-            return;
-        }
-
-        if (state.mThemeAttrs != null) {
-            final TypedArray a = t.resolveAttributes(
-                    state.mThemeAttrs, R.styleable.ScaleDrawable);
-            try {
-                updateStateFromTypedArray(a);
-                verifyRequiredAttributes(a);
-            } catch (XmlPullParserException e) {
-                throw new RuntimeException(e);
-            } finally {
-                a.recycle();
-            }
-        }
-
-        // The drawable may have changed as a result of applying the theme, so
-        // apply the theme to the wrapped drawable last.
-        super.applyTheme(t);
-
-        updateLocalState();
-    }
-
-    @Override
     public void draw(Canvas canvas) {
         final Drawable d = getDrawable();
         if (d != null && d.getLevel() != 0) {
@@ -243,7 +246,7 @@
 
     @Override
     DrawableWrapperState mutateConstantState() {
-        mState = new ScaleState(mState);
+        mState = new ScaleState(mState, null);
         return mState;
     }
 
@@ -251,14 +254,16 @@
         /** Constant used to disable scaling for a particular dimension. */
         private static final float DO_NOT_SCALE = -1.0f;
 
+        private int[] mThemeAttrs;
+
         float mScaleWidth = DO_NOT_SCALE;
         float mScaleHeight = DO_NOT_SCALE;
         int mGravity = Gravity.LEFT;
         boolean mUseIntrinsicSizeAsMin = false;
         int mInitialLevel = 0;
 
-        ScaleState(ScaleState orig) {
-            super(orig);
+        ScaleState(ScaleState orig, Resources res) {
+            super(orig, res);
 
             if (orig != null) {
                 mScaleWidth = orig.mScaleWidth;