Fix NinePatchDrawable handling of density changes

Scaling is now manually applied when the canvas density is not set.

Removes set/getNinePatch accessors that were added in N, since these
are not used and setting the nine patch directly will break padding.
Also removes local NinePatch, which was not necessary.

Bug: 27038114
Change-Id: Ie8b2e485b54d1ed1521081b329a09915fd0a95c1
diff --git a/api/current.txt b/api/current.txt
index 66c1d39..a01c420 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11947,6 +11947,8 @@
     ctor public Outline(android.graphics.Outline);
     method public boolean canClip();
     method public float getAlpha();
+    method public float getRadius();
+    method public boolean getRect(android.graphics.Rect);
     method public boolean isEmpty();
     method public void offset(int, int);
     method public void set(android.graphics.Outline);
@@ -12974,12 +12976,10 @@
     ctor public deprecated NinePatchDrawable(android.graphics.NinePatch);
     ctor public NinePatchDrawable(android.content.res.Resources, android.graphics.NinePatch);
     method public void draw(android.graphics.Canvas);
-    method public android.graphics.NinePatch getNinePatch();
     method public int getOpacity();
     method public android.graphics.Paint getPaint();
     method public void setAlpha(int);
     method public void setColorFilter(android.graphics.ColorFilter);
-    method public void setNinePatch(android.graphics.NinePatch);
     method public void setTargetDensity(android.graphics.Canvas);
     method public void setTargetDensity(android.util.DisplayMetrics);
     method public void setTargetDensity(int);
diff --git a/api/system-current.txt b/api/system-current.txt
index 11e5af5..d4e6d2f 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -12357,6 +12357,8 @@
     ctor public Outline(android.graphics.Outline);
     method public boolean canClip();
     method public float getAlpha();
+    method public float getRadius();
+    method public boolean getRect(android.graphics.Rect);
     method public boolean isEmpty();
     method public void offset(int, int);
     method public void set(android.graphics.Outline);
@@ -13384,12 +13386,10 @@
     ctor public deprecated NinePatchDrawable(android.graphics.NinePatch);
     ctor public NinePatchDrawable(android.content.res.Resources, android.graphics.NinePatch);
     method public void draw(android.graphics.Canvas);
-    method public android.graphics.NinePatch getNinePatch();
     method public int getOpacity();
     method public android.graphics.Paint getPaint();
     method public void setAlpha(int);
     method public void setColorFilter(android.graphics.ColorFilter);
-    method public void setNinePatch(android.graphics.NinePatch);
     method public void setTargetDensity(android.graphics.Canvas);
     method public void setTargetDensity(android.util.DisplayMetrics);
     method public void setTargetDensity(int);
diff --git a/api/test-current.txt b/api/test-current.txt
index a7bc720..6d85eaa 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -11955,6 +11955,8 @@
     ctor public Outline(android.graphics.Outline);
     method public boolean canClip();
     method public float getAlpha();
+    method public float getRadius();
+    method public boolean getRect(android.graphics.Rect);
     method public boolean isEmpty();
     method public void offset(int, int);
     method public void set(android.graphics.Outline);
@@ -12982,12 +12984,10 @@
     ctor public deprecated NinePatchDrawable(android.graphics.NinePatch);
     ctor public NinePatchDrawable(android.content.res.Resources, android.graphics.NinePatch);
     method public void draw(android.graphics.Canvas);
-    method public android.graphics.NinePatch getNinePatch();
     method public int getOpacity();
     method public android.graphics.Paint getPaint();
     method public void setAlpha(int);
     method public void setColorFilter(android.graphics.ColorFilter);
-    method public void setNinePatch(android.graphics.NinePatch);
     method public void setTargetDensity(android.graphics.Canvas);
     method public void setTargetDensity(android.util.DisplayMetrics);
     method public void setTargetDensity(int);
diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java
index aa40408..99fa9fe 100644
--- a/graphics/java/android/graphics/Outline.java
+++ b/graphics/java/android/graphics/Outline.java
@@ -18,6 +18,7 @@
 
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.drawable.Drawable;
 
 /**
@@ -176,6 +177,34 @@
     }
 
     /**
+     * Populates {@code outBounds} with the outline bounds, if set, and returns
+     * {@code true}. If no outline bounds are set, or if a path has been set
+     * via {@link #setConvexPath(Path)}, returns {@code false}.
+     *
+     * @param outRect the rect to populate with the outline bounds, if set
+     * @return {@code true} if {@code outBounds} was populated with outline
+     *         bounds, or {@code false} if no outline bounds are set
+     */
+    public boolean getRect(@NonNull Rect outRect) {
+        if (mRect == null) {
+            return false;
+        }
+        outRect.set(mRect);
+        return true;
+    }
+
+    /**
+     * Returns the rounded rect radius, if set, or {@code -1} if a path has
+     * been set via {@link #setConvexPath(Path)}. A return value of {@code 0}
+     * indicates a non-rounded rect.
+     *
+     * @return the rounded rect radius or {@code -1}
+     */
+    public float getRadius() {
+        return mRadius;
+    }
+
+    /**
      * Sets the outline to the oval defined by input rect.
      */
     public void setOval(int left, int top, int right, int bottom) {
diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index bfbdfa5..6816539 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -66,11 +66,16 @@
 public class NinePatchDrawable extends Drawable {
     // dithering helps a lot, and is pretty cheap, so default is true
     private static final boolean DEFAULT_DITHER = false;
+
+    /** Temporary rect used for density scaling. */
+    private Rect mTempRect;
+
     private NinePatchState mNinePatchState;
-    private NinePatch mNinePatch;
     private PorterDuffColorFilter mTintFilter;
     private Rect mPadding;
     private Insets mOpticalInsets = Insets.NONE;
+    private Rect mOutlineInsets;
+    private float mOutlineRadius;
     private Paint mPaint;
     private boolean mMutated;
 
@@ -86,8 +91,9 @@
 
     /**
      * Create drawable from raw nine-patch data, not dealing with density.
+     *
      * @deprecated Use {@link #NinePatchDrawable(Resources, Bitmap, byte[], Rect, String)}
-     * to ensure that the drawable has correctly set its target density.
+     *             to ensure that the drawable has correctly set its target density.
      */
     @Deprecated
     public NinePatchDrawable(Bitmap bitmap, byte[] chunk, Rect padding, String srcName) {
@@ -101,7 +107,6 @@
     public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk,
             Rect padding, String srcName) {
         this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), res);
-        mNinePatchState.mTargetDensity = mTargetDensity;
     }
 
     /**
@@ -114,16 +119,17 @@
             Rect padding, Rect opticalInsets, String srcName) {
         this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding, opticalInsets),
                 res);
-        mNinePatchState.mTargetDensity = mTargetDensity;
     }
 
     /**
      * Create drawable from existing nine-patch, not dealing with density.
+     *
      * @deprecated Use {@link #NinePatchDrawable(Resources, NinePatch)}
-     * to ensure that the drawable has correctly set its target density.
+     *             to ensure that the drawable has correctly set its target
+     *             density.
      */
     @Deprecated
-    public NinePatchDrawable(NinePatch patch) {
+    public NinePatchDrawable(@NonNull NinePatch patch) {
         this(new NinePatchState(patch, new Rect()), null);
     }
 
@@ -131,9 +137,8 @@
      * Create drawable from existing nine-patch, setting initial target density
      * based on the display metrics of the resources.
      */
-    public NinePatchDrawable(Resources res, NinePatch patch) {
+    public NinePatchDrawable(@Nullable Resources res, @NonNull NinePatch patch) {
         this(new NinePatchState(patch, new Rect()), res);
-        mNinePatchState.mTargetDensity = mTargetDensity;
     }
 
     /**
@@ -146,7 +151,7 @@
      * @see android.graphics.Bitmap#setDensity(int)
      * @see android.graphics.Bitmap#getDensity()
      */
-    public void setTargetDensity(Canvas canvas) {
+    public void setTargetDensity(@NonNull Canvas canvas) {
         setTargetDensity(canvas.getDensity());
     }
 
@@ -158,7 +163,7 @@
      * @see android.graphics.Bitmap#setDensity(int)
      * @see android.graphics.Bitmap#getDensity()
      */
-    public void setTargetDensity(DisplayMetrics metrics) {
+    public void setTargetDensity(@NonNull DisplayMetrics metrics) {
         setTargetDensity(metrics.densityDpi);
     }
 
@@ -171,78 +176,24 @@
      * @see android.graphics.Bitmap#getDensity()
      */
     public void setTargetDensity(int density) {
-        if (density != mTargetDensity) {
-            mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density;
-            if (mNinePatch != null) {
-                computeBitmapSize();
-            }
+        if (density == 0) {
+            density = DisplayMetrics.DENSITY_DEFAULT;
+        }
+
+        if (mTargetDensity != density) {
+            mTargetDensity = density;
+
+            computeBitmapSize();
             invalidateSelf();
         }
     }
 
-    private static Insets scaleFromDensity(Insets insets, int sdensity, int tdensity) {
-        int left = Drawable.scaleFromDensity(insets.left, sdensity, tdensity, true);
-        int top = Drawable.scaleFromDensity(insets.top, sdensity, tdensity, true);
-        int right = Drawable.scaleFromDensity(insets.right, sdensity, tdensity, true);
-        int bottom = Drawable.scaleFromDensity(insets.bottom, sdensity, tdensity, true);
-        return Insets.of(left, top, right, bottom);
-    }
-
-    private void computeBitmapSize() {
-        final int sdensity = mNinePatch.getDensity();
-        final int tdensity = mTargetDensity;
-        if (sdensity == tdensity) {
-            mBitmapWidth = mNinePatch.getWidth();
-            mBitmapHeight = mNinePatch.getHeight();
-            mOpticalInsets = mNinePatchState.mOpticalInsets;
-        } else {
-            mBitmapWidth = Drawable.scaleFromDensity(
-                    mNinePatch.getWidth(), sdensity, tdensity, true);
-            mBitmapHeight = Drawable.scaleFromDensity(
-                    mNinePatch.getHeight(), sdensity, tdensity, true);
-            if (mNinePatchState.mPadding != null && mPadding != null) {
-                Rect dest = mPadding;
-                Rect src = mNinePatchState.mPadding;
-                if (dest == src) {
-                    mPadding = dest = new Rect(src);
-                }
-                dest.left = Drawable.scaleFromDensity(src.left, sdensity, tdensity, true);
-                dest.top = Drawable.scaleFromDensity(src.top, sdensity, tdensity, true);
-                dest.right = Drawable.scaleFromDensity(src.right, sdensity, tdensity, true);
-                dest.bottom = Drawable.scaleFromDensity(src.bottom, sdensity, tdensity, true);
-            }
-            mOpticalInsets = scaleFromDensity(mNinePatchState.mOpticalInsets, sdensity, tdensity);
-        }
-    }
-
-    /**
-     * Sets the nine patch used by this drawable.
-     *
-     * @param ninePatch the nine patch for this drawable
-     */
-    public void setNinePatch(NinePatch ninePatch) {
-        if (mNinePatch != ninePatch) {
-            mNinePatch = ninePatch;
-            if (ninePatch != null) {
-                computeBitmapSize();
-            } else {
-                mBitmapWidth = mBitmapHeight = -1;
-                mOpticalInsets = Insets.NONE;
-            }
-            invalidateSelf();
-        }
-    }
-
-    /**
-     * @return the nine patch used by this drawable
-     */
-    public NinePatch getNinePatch() {
-        return mNinePatch;
-    }
-
     @Override
     public void draw(Canvas canvas) {
-        final Rect bounds = getBounds();
+        final NinePatchState state = mNinePatchState;
+
+        Rect bounds = getBounds();
+        int restoreToCount = -1;
 
         final boolean clearColorFilter;
         if (mTintFilter != null && getPaint().getColorFilter() == null) {
@@ -252,22 +203,52 @@
             clearColorFilter = false;
         }
 
-        final boolean needsMirroring = needsMirroring();
-        if (needsMirroring) {
-            // Mirror the 9patch
-            canvas.translate(bounds.right - bounds.left, 0);
-            canvas.scale(-1.0f, 1.0f);
-        }
-
         final int restoreAlpha;
-        if (mNinePatchState.mBaseAlpha != 1.0f) {
-            restoreAlpha = mPaint.getAlpha();
-            mPaint.setAlpha((int) (restoreAlpha * mNinePatchState.mBaseAlpha + 0.5f));
+        if (state.mBaseAlpha != 1.0f) {
+            restoreAlpha = getPaint().getAlpha();
+            mPaint.setAlpha((int) (restoreAlpha * state.mBaseAlpha + 0.5f));
         } else {
             restoreAlpha = -1;
         }
 
-        mNinePatch.draw(canvas, bounds, mPaint);
+        final boolean needsDensityScaling = canvas.getDensity() == 0;
+        if (needsDensityScaling) {
+            restoreToCount = restoreToCount >= 0 ? restoreToCount : canvas.save();
+
+            // Apply density scaling.
+            final float scale = mTargetDensity / (float) state.mNinePatch.getDensity();
+            final float px = bounds.left;
+            final float py = bounds.top;
+            canvas.scale(scale, scale, px, py);
+
+            if (mTempRect == null) {
+                mTempRect = new Rect();
+            }
+
+            // Scale the bounds to match.
+            final Rect scaledBounds = mTempRect;
+            scaledBounds.left = bounds.left;
+            scaledBounds.top = bounds.top;
+            scaledBounds.right = bounds.left + Math.round(bounds.width() / scale);
+            scaledBounds.bottom = bounds.top + Math.round(bounds.height() / scale);
+            bounds = scaledBounds;
+        }
+
+        final boolean needsMirroring = needsMirroring();
+        if (needsMirroring) {
+            restoreToCount = restoreToCount >= 0 ? restoreToCount : canvas.save();
+
+            // Mirror the 9patch.
+            final float cx = (bounds.left + bounds.right) / 2.0f;
+            final float cy = (bounds.top + bounds.bottom) / 2.0f;
+            canvas.scale(-1.0f, 1.0f, cx, cy);
+        }
+
+        state.mNinePatch.draw(canvas, bounds, mPaint);
+
+        if (restoreToCount >= 0) {
+            canvas.restoreToCount(restoreToCount);
+        }
 
         if (clearColorFilter) {
             mPaint.setColorFilter(null);
@@ -284,38 +265,36 @@
     }
 
     @Override
-    public boolean getPadding(Rect padding) {
-        final Rect scaledPadding = mPadding;
-        if (scaledPadding != null) {
-            if (needsMirroring()) {
-                padding.set(scaledPadding.right, scaledPadding.top,
-                        scaledPadding.left, scaledPadding.bottom);
-            } else {
-                padding.set(scaledPadding);
-            }
+    public boolean getPadding(@NonNull Rect padding) {
+        if (mPadding != null) {
+            padding.set(mPadding);
             return (padding.left | padding.top | padding.right | padding.bottom) != 0;
+        } else {
+            return super.getPadding(padding);
         }
-        return false;
     }
 
     @Override
     public void getOutline(@NonNull Outline outline) {
         final Rect bounds = getBounds();
-        if (bounds.isEmpty()) return;
+        if (bounds.isEmpty()) {
+            return;
+        }
 
-        if (mNinePatchState != null) {
-            NinePatch.InsetStruct insets = mNinePatchState.mNinePatch.getBitmap().getNinePatchInsets();
+        if (mNinePatchState != null && mOutlineInsets != null) {
+            final NinePatch.InsetStruct insets =
+                    mNinePatchState.mNinePatch.getBitmap().getNinePatchInsets();
             if (insets != null) {
-                final Rect outlineInsets = insets.outlineRect;
-                outline.setRoundRect(bounds.left + outlineInsets.left,
-                        bounds.top + outlineInsets.top,
-                        bounds.right - outlineInsets.right,
-                        bounds.bottom - outlineInsets.bottom,
-                        insets.outlineRadius);
+                outline.setRoundRect(bounds.left + mOutlineInsets.left,
+                        bounds.top + mOutlineInsets.top,
+                        bounds.right - mOutlineInsets.right,
+                        bounds.bottom - mOutlineInsets.bottom,
+                        mOutlineRadius);
                 outline.setAlpha(insets.outlineAlpha * (getAlpha() / 255.0f));
                 return;
             }
         }
+
         super.getOutline(outline);
     }
 
@@ -324,11 +303,12 @@
      */
     @Override
     public Insets getOpticalInsets() {
+        final Insets opticalInsets = mOpticalInsets;
         if (needsMirroring()) {
-            return Insets.of(mOpticalInsets.right, mOpticalInsets.top,
-                    mOpticalInsets.left, mOpticalInsets.bottom);
+            return Insets.of(opticalInsets.right, opticalInsets.top,
+                    opticalInsets.left, opticalInsets.bottom);
         } else {
-            return mOpticalInsets;
+            return opticalInsets;
         }
     }
 
@@ -352,7 +332,7 @@
     }
 
     @Override
-    public void setColorFilter(ColorFilter colorFilter) {
+    public void setColorFilter(@Nullable ColorFilter colorFilter) {
         if (mPaint == null && colorFilter == null) {
             // Fast common case -- leave at no color filter.
             return;
@@ -362,14 +342,14 @@
     }
 
     @Override
-    public void setTintList(ColorStateList tint) {
+    public void setTintList(@Nullable ColorStateList tint) {
         mNinePatchState.mTint = tint;
         mTintFilter = updateTintFilter(mTintFilter, tint, mNinePatchState.mTintMode);
         invalidateSelf();
     }
 
     @Override
-    public void setTintMode(PorterDuff.Mode tintMode) {
+    public void setTintMode(@Nullable PorterDuff.Mode tintMode) {
         mNinePatchState.mTintMode = tintMode;
         mTintFilter = updateTintFilter(mTintFilter, mNinePatchState.mTint, tintMode);
         invalidateSelf();
@@ -409,10 +389,7 @@
 
     @Override
     public boolean isFilterBitmap() {
-        if (mPaint == null) {
-            return false;
-        }
-        return getPaint().isFilterBitmap();
+        return mPaint != null && getPaint().isFilterBitmap();
     }
 
     @Override
@@ -430,7 +407,7 @@
     /**
      * Updates the constant state from the values in the typed array.
      */
-    private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
+    private void updateStateFromTypedArray(@NonNull TypedArray a) throws XmlPullParserException {
         final Resources r = a.getResources();
         final NinePatchState state = mNinePatchState;
 
@@ -491,12 +468,10 @@
         if (tint != null) {
             state.mTint = tint;
         }
-
-        state.mTargetDensity = Drawable.resolveDensity(r, state.mTargetDensity);
     }
 
     @Override
-    public void applyTheme(Theme t) {
+    public void applyTheme(@NonNull Theme t) {
         super.applyTheme(t);
 
         final NinePatchState state = mNinePatchState;
@@ -528,6 +503,7 @@
         return mNinePatchState != null && mNinePatchState.canApplyTheme();
     }
 
+    @NonNull
     public Paint getPaint() {
         if (mPaint == null) {
             mPaint = new Paint();
@@ -536,45 +512,26 @@
         return mPaint;
     }
 
-    /**
-     * Retrieves the width of the source .png file (before resizing).
-     */
     @Override
     public int getIntrinsicWidth() {
         return mBitmapWidth;
     }
 
-    /**
-     * Retrieves the height of the source .png file (before resizing).
-     */
     @Override
     public int getIntrinsicHeight() {
         return mBitmapHeight;
     }
 
     @Override
-    public int getMinimumWidth() {
-        return mBitmapWidth;
-    }
-
-    @Override
-    public int getMinimumHeight() {
-        return mBitmapHeight;
-    }
-
-    /**
-     * Returns a {@link android.graphics.PixelFormat graphics.PixelFormat}
-     * value of OPAQUE or TRANSLUCENT.
-     */
-    @Override
     public int getOpacity() {
-        return mNinePatch.hasAlpha() || (mPaint != null && mPaint.getAlpha() < 255) ?
-                PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
+        return mNinePatchState.mNinePatch.hasAlpha()
+                || (mPaint != null && mPaint.getAlpha() < 255) ?
+                        PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
     }
 
     @Override
     public Region getTransparentRegion() {
-        return mNinePatch.getTransparentRegion(getBounds());
+        return mNinePatchState.mNinePatch.getTransparentRegion(getBounds());
     }
 
     @Override
@@ -587,7 +544,6 @@
     public Drawable mutate() {
         if (!mMutated && super.mutate() == this) {
             mNinePatchState = new NinePatchState(mNinePatchState);
-            mNinePatch = mNinePatchState.mNinePatch;
             mMutated = true;
         }
         return this;
@@ -619,8 +575,9 @@
     }
 
     final static class NinePatchState extends ConstantState {
+        int mChangingConfigurations;
+
         // Values loaded during inflation.
-        int[] mThemeAttrs = null;
         NinePatch mNinePatch = null;
         ColorStateList mTint = null;
         Mode mTintMode = DEFAULT_TINT_MODE;
@@ -628,10 +585,9 @@
         Insets mOpticalInsets = Insets.NONE;
         float mBaseAlpha = 1.0f;
         boolean mDither = DEFAULT_DITHER;
-        int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
         boolean mAutoMirrored = false;
 
-        int mChangingConfigurations;
+        int[] mThemeAttrs;
 
         NinePatchState() {
             // Empty constructor.
@@ -655,27 +611,24 @@
             mAutoMirrored = autoMirror;
         }
 
-        // Copy constructor
-
-        NinePatchState(@NonNull NinePatchState state) {
-            // We don't deep-copy any fields because they are all immutable.
-            mNinePatch = state.mNinePatch;
-            mTint = state.mTint;
-            mTintMode = state.mTintMode;
-            mThemeAttrs = state.mThemeAttrs;
-            mPadding = state.mPadding;
-            mOpticalInsets = state.mOpticalInsets;
-            mBaseAlpha = state.mBaseAlpha;
-            mDither = state.mDither;
-            mChangingConfigurations = state.mChangingConfigurations;
-            mTargetDensity = state.mTargetDensity;
-            mAutoMirrored = state.mAutoMirrored;
+        NinePatchState(@NonNull NinePatchState orig) {
+            mChangingConfigurations = orig.mChangingConfigurations;
+            mNinePatch = orig.mNinePatch;
+            mTint = orig.mTint;
+            mTintMode = orig.mTintMode;
+            mPadding = orig.mPadding;
+            mOpticalInsets = orig.mOpticalInsets;
+            mBaseAlpha = orig.mBaseAlpha;
+            mDither = orig.mDither;
+            mAutoMirrored = orig.mAutoMirrored;
+            mThemeAttrs = orig.mThemeAttrs;
         }
 
         @Override
         public boolean canApplyTheme() {
             return mThemeAttrs != null
-                    || (mTint != null && mTint.canApplyTheme());
+                    || (mTint != null && mTint.canApplyTheme())
+                    || super.canApplyTheme();
         }
 
         @Override
@@ -704,44 +657,98 @@
         }
     }
 
+    private void computeBitmapSize() {
+        final NinePatch ninePatch = mNinePatchState.mNinePatch;
+        if (ninePatch == null) {
+            return;
+        }
+
+        final int sourceDensity = ninePatch.getDensity();
+        final int targetDensity = mTargetDensity;
+
+        final Insets sourceOpticalInsets = mNinePatchState.mOpticalInsets;
+        if (sourceOpticalInsets != Insets.NONE) {
+            final int left = Drawable.scaleFromDensity(
+                    sourceOpticalInsets.left, sourceDensity, targetDensity, true);
+            final int top = Drawable.scaleFromDensity(
+                    sourceOpticalInsets.top, sourceDensity, targetDensity, true);
+            final int right = Drawable.scaleFromDensity(
+                    sourceOpticalInsets.right, sourceDensity, targetDensity, true);
+            final int bottom = Drawable.scaleFromDensity(
+                    sourceOpticalInsets.bottom, sourceDensity, targetDensity, true);
+            mOpticalInsets = Insets.of(left, top, right, bottom);
+        } else {
+            mOpticalInsets = Insets.NONE;
+        }
+
+        final Rect sourcePadding = mNinePatchState.mPadding;
+        if (sourcePadding != null) {
+            if (mPadding == null) {
+                mPadding = new Rect();
+            }
+            mPadding.left = Drawable.scaleFromDensity(
+                    sourcePadding.left, sourceDensity, targetDensity, false);
+            mPadding.top = Drawable.scaleFromDensity(
+                    sourcePadding.top, sourceDensity, targetDensity, false);
+            mPadding.right = Drawable.scaleFromDensity(
+                    sourcePadding.right, sourceDensity, targetDensity, false);
+            mPadding.bottom = Drawable.scaleFromDensity(
+                    sourcePadding.bottom, sourceDensity, targetDensity, false);
+        } else {
+            mPadding = null;
+        }
+
+        mBitmapHeight = Drawable.scaleFromDensity(
+                ninePatch.getHeight(), sourceDensity, targetDensity, true);
+        mBitmapWidth = Drawable.scaleFromDensity(
+                ninePatch.getWidth(), sourceDensity, targetDensity, true);
+
+        final NinePatch.InsetStruct insets = ninePatch.getBitmap().getNinePatchInsets();
+        if (insets != null) {
+            if (mOutlineInsets == null) {
+                mOutlineInsets = new Rect();
+            }
+            final Rect outlineInsets = insets.outlineRect;
+            mOutlineInsets.left = Drawable.scaleFromDensity(
+                    outlineInsets.left, sourceDensity, targetDensity, false);
+            mOutlineInsets.top = Drawable.scaleFromDensity(
+                    outlineInsets.top, sourceDensity, targetDensity, false);
+            mOutlineInsets.right = Drawable.scaleFromDensity(
+                    outlineInsets.right, sourceDensity, targetDensity, false);
+            mOutlineInsets.bottom = Drawable.scaleFromDensity(
+                    outlineInsets.bottom, sourceDensity, targetDensity, false);
+            mOutlineRadius = Drawable.scaleFromDensity(
+                    insets.outlineRadius, sourceDensity, targetDensity);
+        } else {
+            mOutlineInsets = null;
+        }
+    }
+
     /**
      * The one constructor to rule them all. This is called by all public
      * constructors to set the state and initialize local properties.
+     *
+     * @param state constant state to assign to the new drawable
      */
-    private NinePatchDrawable(NinePatchState state, Resources res) {
+    private NinePatchDrawable(@NonNull NinePatchState state, @Nullable Resources res) {
         mNinePatchState = state;
 
         updateLocalState(res);
-
-        // Push density applied by setNinePatchState into state.
-        mNinePatchState.mTargetDensity = mTargetDensity;
     }
 
     /**
      * Initializes local dynamic properties from state.
      */
-    private void updateLocalState(Resources res) {
+    private void updateLocalState(@Nullable Resources res) {
         final NinePatchState state = mNinePatchState;
 
-        if (res != null) {
-            final int densityDpi = res.getDisplayMetrics().densityDpi;
-            mTargetDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
-        } else {
-            mTargetDensity = state.mTargetDensity;
-        }
-
-
         // If we can, avoid calling any methods that initialize Paint.
         if (state.mDither != DEFAULT_DITHER) {
             setDither(state.mDither);
         }
 
-        // Make a local copy of the padding.
-        if (state.mPadding != null) {
-            mPadding = new Rect(state.mPadding);
-        }
-
+        mTargetDensity = Drawable.resolveDensity(res, mTargetDensity);
         mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
-        setNinePatch(state.mNinePatch);
+        computeBitmapSize();
     }
 }