1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.graphics.drawable;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.content.pm.ActivityInfo.Config;
22import android.content.res.ColorStateList;
23import android.content.res.Resources;
24import android.content.res.Resources.Theme;
25import android.content.res.TypedArray;
26import android.graphics.Bitmap;
27import android.graphics.Canvas;
28import android.graphics.ColorFilter;
29import android.graphics.ImageDecoder;
30import android.graphics.Insets;
31import android.graphics.NinePatch;
32import android.graphics.Outline;
33import android.graphics.Paint;
34import android.graphics.PixelFormat;
35import android.graphics.PorterDuff;
36import android.graphics.PorterDuff.Mode;
37import android.graphics.PorterDuffColorFilter;
38import android.graphics.Rect;
39import android.graphics.Region;
40import android.util.AttributeSet;
41import android.util.DisplayMetrics;
42import android.util.LayoutDirection;
43import android.util.TypedValue;
44
45import com.android.internal.R;
46
47import org.xmlpull.v1.XmlPullParser;
48import org.xmlpull.v1.XmlPullParserException;
49
50import java.io.IOException;
51import java.io.InputStream;
52
53/**
54 *
55 * A resizeable bitmap, with stretchable areas that you define. This type of image
56 * is defined in a .png file with a special format.
57 *
58 * <div class="special reference">
59 * <h3>Developer Guides</h3>
60 * <p>For more information about how to use a NinePatchDrawable, read the
61 * <a href="{@docRoot}guide/topics/graphics/2d-graphics.html#nine-patch">
62 * Canvas and Drawables</a> developer guide. For information about creating a NinePatch image
63 * file using the draw9patch tool, see the
64 * <a href="{@docRoot}guide/developing/tools/draw9patch.html">Draw 9-patch</a> tool guide.</p></div>
65 */
66public class NinePatchDrawable extends Drawable {
67    // dithering helps a lot, and is pretty cheap, so default is true
68    private static final boolean DEFAULT_DITHER = false;
69
70    /** Temporary rect used for density scaling. */
71    private Rect mTempRect;
72
73    private NinePatchState mNinePatchState;
74    private PorterDuffColorFilter mTintFilter;
75    private Rect mPadding;
76    private Insets mOpticalInsets = Insets.NONE;
77    private Rect mOutlineInsets;
78    private float mOutlineRadius;
79    private Paint mPaint;
80    private boolean mMutated;
81
82    private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
83
84    // These are scaled to match the target density.
85    private int mBitmapWidth = -1;
86    private int mBitmapHeight = -1;
87
88    NinePatchDrawable() {
89        mNinePatchState = new NinePatchState();
90    }
91
92    /**
93     * Create drawable from raw nine-patch data, not dealing with density.
94     *
95     * @deprecated Use {@link #NinePatchDrawable(Resources, Bitmap, byte[], Rect, String)}
96     *             to ensure that the drawable has correctly set its target density.
97     */
98    @Deprecated
99    public NinePatchDrawable(Bitmap bitmap, byte[] chunk, Rect padding, String srcName) {
100        this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), null);
101    }
102
103    /**
104     * Create drawable from raw nine-patch data, setting initial target density
105     * based on the display metrics of the resources.
106     */
107    public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk,
108            Rect padding, String srcName) {
109        this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), res);
110    }
111
112    /**
113     * Create drawable from raw nine-patch data, setting initial target density
114     * based on the display metrics of the resources.
115     *
116     * @hide
117     */
118    public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk,
119            Rect padding, Rect opticalInsets, String srcName) {
120        this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding, opticalInsets),
121                res);
122    }
123
124    /**
125     * Create drawable from existing nine-patch, not dealing with density.
126     *
127     * @deprecated Use {@link #NinePatchDrawable(Resources, NinePatch)}
128     *             to ensure that the drawable has correctly set its target
129     *             density.
130     */
131    @Deprecated
132    public NinePatchDrawable(@NonNull NinePatch patch) {
133        this(new NinePatchState(patch, new Rect()), null);
134    }
135
136    /**
137     * Create drawable from existing nine-patch, setting initial target density
138     * based on the display metrics of the resources.
139     */
140    public NinePatchDrawable(@Nullable Resources res, @NonNull NinePatch patch) {
141        this(new NinePatchState(patch, new Rect()), res);
142    }
143
144    /**
145     * Set the density scale at which this drawable will be rendered. This
146     * method assumes the drawable will be rendered at the same density as the
147     * specified canvas.
148     *
149     * @param canvas The Canvas from which the density scale must be obtained.
150     *
151     * @see android.graphics.Bitmap#setDensity(int)
152     * @see android.graphics.Bitmap#getDensity()
153     */
154    public void setTargetDensity(@NonNull Canvas canvas) {
155        setTargetDensity(canvas.getDensity());
156    }
157
158    /**
159     * Set the density scale at which this drawable will be rendered.
160     *
161     * @param metrics The DisplayMetrics indicating the density scale for this drawable.
162     *
163     * @see android.graphics.Bitmap#setDensity(int)
164     * @see android.graphics.Bitmap#getDensity()
165     */
166    public void setTargetDensity(@NonNull DisplayMetrics metrics) {
167        setTargetDensity(metrics.densityDpi);
168    }
169
170    /**
171     * Set the density at which this drawable will be rendered.
172     *
173     * @param density The density scale for this drawable.
174     *
175     * @see android.graphics.Bitmap#setDensity(int)
176     * @see android.graphics.Bitmap#getDensity()
177     */
178    public void setTargetDensity(int density) {
179        if (density == 0) {
180            density = DisplayMetrics.DENSITY_DEFAULT;
181        }
182
183        if (mTargetDensity != density) {
184            mTargetDensity = density;
185
186            computeBitmapSize();
187            invalidateSelf();
188        }
189    }
190
191    @Override
192    public void draw(Canvas canvas) {
193        final NinePatchState state = mNinePatchState;
194
195        Rect bounds = getBounds();
196        int restoreToCount = -1;
197
198        final boolean clearColorFilter;
199        if (mTintFilter != null && getPaint().getColorFilter() == null) {
200            mPaint.setColorFilter(mTintFilter);
201            clearColorFilter = true;
202        } else {
203            clearColorFilter = false;
204        }
205
206        final int restoreAlpha;
207        if (state.mBaseAlpha != 1.0f) {
208            restoreAlpha = getPaint().getAlpha();
209            mPaint.setAlpha((int) (restoreAlpha * state.mBaseAlpha + 0.5f));
210        } else {
211            restoreAlpha = -1;
212        }
213
214        final boolean needsDensityScaling = canvas.getDensity() == 0
215                && Bitmap.DENSITY_NONE != state.mNinePatch.getDensity();
216        if (needsDensityScaling) {
217            restoreToCount = restoreToCount >= 0 ? restoreToCount : canvas.save();
218
219            // Apply density scaling.
220            final float scale = mTargetDensity / (float) state.mNinePatch.getDensity();
221            final float px = bounds.left;
222            final float py = bounds.top;
223            canvas.scale(scale, scale, px, py);
224
225            if (mTempRect == null) {
226                mTempRect = new Rect();
227            }
228
229            // Scale the bounds to match.
230            final Rect scaledBounds = mTempRect;
231            scaledBounds.left = bounds.left;
232            scaledBounds.top = bounds.top;
233            scaledBounds.right = bounds.left + Math.round(bounds.width() / scale);
234            scaledBounds.bottom = bounds.top + Math.round(bounds.height() / scale);
235            bounds = scaledBounds;
236        }
237
238        final boolean needsMirroring = needsMirroring();
239        if (needsMirroring) {
240            restoreToCount = restoreToCount >= 0 ? restoreToCount : canvas.save();
241
242            // Mirror the 9patch.
243            final float cx = (bounds.left + bounds.right) / 2.0f;
244            final float cy = (bounds.top + bounds.bottom) / 2.0f;
245            canvas.scale(-1.0f, 1.0f, cx, cy);
246        }
247
248        state.mNinePatch.draw(canvas, bounds, mPaint);
249
250        if (restoreToCount >= 0) {
251            canvas.restoreToCount(restoreToCount);
252        }
253
254        if (clearColorFilter) {
255            mPaint.setColorFilter(null);
256        }
257
258        if (restoreAlpha >= 0) {
259            mPaint.setAlpha(restoreAlpha);
260        }
261    }
262
263    @Override
264    public @Config int getChangingConfigurations() {
265        return super.getChangingConfigurations() | mNinePatchState.getChangingConfigurations();
266    }
267
268    @Override
269    public boolean getPadding(@NonNull Rect padding) {
270        if (mPadding != null) {
271            padding.set(mPadding);
272            return (padding.left | padding.top | padding.right | padding.bottom) != 0;
273        } else {
274            return super.getPadding(padding);
275        }
276    }
277
278    @Override
279    public void getOutline(@NonNull Outline outline) {
280        final Rect bounds = getBounds();
281        if (bounds.isEmpty()) {
282            return;
283        }
284
285        if (mNinePatchState != null && mOutlineInsets != null) {
286            final NinePatch.InsetStruct insets =
287                    mNinePatchState.mNinePatch.getBitmap().getNinePatchInsets();
288            if (insets != null) {
289                outline.setRoundRect(bounds.left + mOutlineInsets.left,
290                        bounds.top + mOutlineInsets.top,
291                        bounds.right - mOutlineInsets.right,
292                        bounds.bottom - mOutlineInsets.bottom,
293                        mOutlineRadius);
294                outline.setAlpha(insets.outlineAlpha * (getAlpha() / 255.0f));
295                return;
296            }
297        }
298
299        super.getOutline(outline);
300    }
301
302    /**
303     * @hide
304     */
305    @Override
306    public Insets getOpticalInsets() {
307        final Insets opticalInsets = mOpticalInsets;
308        if (needsMirroring()) {
309            return Insets.of(opticalInsets.right, opticalInsets.top,
310                    opticalInsets.left, opticalInsets.bottom);
311        } else {
312            return opticalInsets;
313        }
314    }
315
316    @Override
317    public void setAlpha(int alpha) {
318        if (mPaint == null && alpha == 0xFF) {
319            // Fast common case -- leave at normal alpha.
320            return;
321        }
322        getPaint().setAlpha(alpha);
323        invalidateSelf();
324    }
325
326    @Override
327    public int getAlpha() {
328        if (mPaint == null) {
329            // Fast common case -- normal alpha.
330            return 0xFF;
331        }
332        return getPaint().getAlpha();
333    }
334
335    @Override
336    public void setColorFilter(@Nullable ColorFilter colorFilter) {
337        if (mPaint == null && colorFilter == null) {
338            // Fast common case -- leave at no color filter.
339            return;
340        }
341        getPaint().setColorFilter(colorFilter);
342        invalidateSelf();
343    }
344
345    @Override
346    public void setTintList(@Nullable ColorStateList tint) {
347        mNinePatchState.mTint = tint;
348        mTintFilter = updateTintFilter(mTintFilter, tint, mNinePatchState.mTintMode);
349        invalidateSelf();
350    }
351
352    @Override
353    public void setTintMode(@Nullable PorterDuff.Mode tintMode) {
354        mNinePatchState.mTintMode = tintMode;
355        mTintFilter = updateTintFilter(mTintFilter, mNinePatchState.mTint, tintMode);
356        invalidateSelf();
357    }
358
359    @Override
360    public void setDither(boolean dither) {
361        //noinspection PointlessBooleanExpression
362        if (mPaint == null && dither == DEFAULT_DITHER) {
363            // Fast common case -- leave at default dither.
364            return;
365        }
366
367        getPaint().setDither(dither);
368        invalidateSelf();
369    }
370
371    @Override
372    public void setAutoMirrored(boolean mirrored) {
373        mNinePatchState.mAutoMirrored = mirrored;
374    }
375
376    private boolean needsMirroring() {
377        return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
378    }
379
380    @Override
381    public boolean isAutoMirrored() {
382        return mNinePatchState.mAutoMirrored;
383    }
384
385    @Override
386    public void setFilterBitmap(boolean filter) {
387        getPaint().setFilterBitmap(filter);
388        invalidateSelf();
389    }
390
391    @Override
392    public boolean isFilterBitmap() {
393        return mPaint != null && getPaint().isFilterBitmap();
394    }
395
396    @Override
397    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
398            throws XmlPullParserException, IOException {
399        super.inflate(r, parser, attrs, theme);
400
401        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.NinePatchDrawable);
402        updateStateFromTypedArray(a);
403        a.recycle();
404
405        updateLocalState(r);
406    }
407
408    /**
409     * Updates the constant state from the values in the typed array.
410     */
411    private void updateStateFromTypedArray(@NonNull TypedArray a) throws XmlPullParserException {
412        final Resources r = a.getResources();
413        final NinePatchState state = mNinePatchState;
414
415        // Account for any configuration changes.
416        state.mChangingConfigurations |= a.getChangingConfigurations();
417
418        // Extract the theme attributes, if any.
419        state.mThemeAttrs = a.extractThemeAttrs();
420
421        state.mDither = a.getBoolean(R.styleable.NinePatchDrawable_dither, state.mDither);
422
423        final int srcResId = a.getResourceId(R.styleable.NinePatchDrawable_src, 0);
424        if (srcResId != 0) {
425            final Rect padding = new Rect();
426            final Rect opticalInsets = new Rect();
427            Bitmap bitmap = null;
428
429            try {
430                final TypedValue value = new TypedValue();
431                final InputStream is = r.openRawResource(srcResId, value);
432
433                int density = Bitmap.DENSITY_NONE;
434                if (value.density == TypedValue.DENSITY_DEFAULT) {
435                    density = DisplayMetrics.DENSITY_DEFAULT;
436                } else if (value.density != TypedValue.DENSITY_NONE) {
437                    density = value.density;
438                }
439                ImageDecoder.Source source = ImageDecoder.createSource(r, is, density);
440                bitmap = ImageDecoder.decodeBitmap(source, (decoder, info, src) -> {
441                    decoder.setOutPaddingRect(padding);
442                    decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
443                });
444
445                is.close();
446            } catch (IOException e) {
447                // Ignore
448            }
449
450            if (bitmap == null) {
451                throw new XmlPullParserException(a.getPositionDescription() +
452                        ": <nine-patch> requires a valid src attribute");
453            } else if (bitmap.getNinePatchChunk() == null) {
454                throw new XmlPullParserException(a.getPositionDescription() +
455                        ": <nine-patch> requires a valid 9-patch source image");
456            }
457
458            bitmap.getOpticalInsets(opticalInsets);
459
460            state.mNinePatch = new NinePatch(bitmap, bitmap.getNinePatchChunk());
461            state.mPadding = padding;
462            state.mOpticalInsets = Insets.of(opticalInsets);
463        }
464
465        state.mAutoMirrored = a.getBoolean(
466                R.styleable.NinePatchDrawable_autoMirrored, state.mAutoMirrored);
467        state.mBaseAlpha = a.getFloat(R.styleable.NinePatchDrawable_alpha, state.mBaseAlpha);
468
469        final int tintMode = a.getInt(R.styleable.NinePatchDrawable_tintMode, -1);
470        if (tintMode != -1) {
471            state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
472        }
473
474        final ColorStateList tint = a.getColorStateList(R.styleable.NinePatchDrawable_tint);
475        if (tint != null) {
476            state.mTint = tint;
477        }
478    }
479
480    @Override
481    public void applyTheme(@NonNull Theme t) {
482        super.applyTheme(t);
483
484        final NinePatchState state = mNinePatchState;
485        if (state == null) {
486            return;
487        }
488
489        if (state.mThemeAttrs != null) {
490            final TypedArray a = t.resolveAttributes(
491                    state.mThemeAttrs, R.styleable.NinePatchDrawable);
492            try {
493                updateStateFromTypedArray(a);
494            } catch (XmlPullParserException e) {
495                rethrowAsRuntimeException(e);
496            } finally {
497                a.recycle();
498            }
499        }
500
501        if (state.mTint != null && state.mTint.canApplyTheme()) {
502            state.mTint = state.mTint.obtainForTheme(t);
503        }
504
505        updateLocalState(t.getResources());
506    }
507
508    @Override
509    public boolean canApplyTheme() {
510        return mNinePatchState != null && mNinePatchState.canApplyTheme();
511    }
512
513    @NonNull
514    public Paint getPaint() {
515        if (mPaint == null) {
516            mPaint = new Paint();
517            mPaint.setDither(DEFAULT_DITHER);
518        }
519        return mPaint;
520    }
521
522    @Override
523    public int getIntrinsicWidth() {
524        return mBitmapWidth;
525    }
526
527    @Override
528    public int getIntrinsicHeight() {
529        return mBitmapHeight;
530    }
531
532    @Override
533    public int getOpacity() {
534        return mNinePatchState.mNinePatch.hasAlpha()
535                || (mPaint != null && mPaint.getAlpha() < 255) ?
536                        PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
537    }
538
539    @Override
540    public Region getTransparentRegion() {
541        return mNinePatchState.mNinePatch.getTransparentRegion(getBounds());
542    }
543
544    @Override
545    public ConstantState getConstantState() {
546        mNinePatchState.mChangingConfigurations = getChangingConfigurations();
547        return mNinePatchState;
548    }
549
550    @Override
551    public Drawable mutate() {
552        if (!mMutated && super.mutate() == this) {
553            mNinePatchState = new NinePatchState(mNinePatchState);
554            mMutated = true;
555        }
556        return this;
557    }
558
559    /**
560     * @hide
561     */
562    public void clearMutated() {
563        super.clearMutated();
564        mMutated = false;
565    }
566
567    @Override
568    protected boolean onStateChange(int[] stateSet) {
569        final NinePatchState state = mNinePatchState;
570        if (state.mTint != null && state.mTintMode != null) {
571            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
572            return true;
573        }
574
575        return false;
576    }
577
578    @Override
579    public boolean isStateful() {
580        final NinePatchState s = mNinePatchState;
581        return super.isStateful() || (s.mTint != null && s.mTint.isStateful());
582    }
583
584    /** @hide */
585    @Override
586    public boolean hasFocusStateSpecified() {
587        return mNinePatchState.mTint != null && mNinePatchState.mTint.hasFocusStateSpecified();
588    }
589
590    final static class NinePatchState extends ConstantState {
591        @Config int mChangingConfigurations;
592
593        // Values loaded during inflation.
594        NinePatch mNinePatch = null;
595        ColorStateList mTint = null;
596        Mode mTintMode = DEFAULT_TINT_MODE;
597        Rect mPadding = null;
598        Insets mOpticalInsets = Insets.NONE;
599        float mBaseAlpha = 1.0f;
600        boolean mDither = DEFAULT_DITHER;
601        boolean mAutoMirrored = false;
602
603        int[] mThemeAttrs;
604
605        NinePatchState() {
606            // Empty constructor.
607        }
608
609        NinePatchState(@NonNull NinePatch ninePatch, @Nullable Rect padding) {
610            this(ninePatch, padding, null, DEFAULT_DITHER, false);
611        }
612
613        NinePatchState(@NonNull NinePatch ninePatch, @Nullable Rect padding,
614                @Nullable Rect opticalInsets) {
615            this(ninePatch, padding, opticalInsets, DEFAULT_DITHER, false);
616        }
617
618        NinePatchState(@NonNull NinePatch ninePatch, @Nullable Rect padding,
619                @Nullable Rect opticalInsets, boolean dither, boolean autoMirror) {
620            mNinePatch = ninePatch;
621            mPadding = padding;
622            mOpticalInsets = Insets.of(opticalInsets);
623            mDither = dither;
624            mAutoMirrored = autoMirror;
625        }
626
627        NinePatchState(@NonNull NinePatchState orig) {
628            mChangingConfigurations = orig.mChangingConfigurations;
629            mNinePatch = orig.mNinePatch;
630            mTint = orig.mTint;
631            mTintMode = orig.mTintMode;
632            mPadding = orig.mPadding;
633            mOpticalInsets = orig.mOpticalInsets;
634            mBaseAlpha = orig.mBaseAlpha;
635            mDither = orig.mDither;
636            mAutoMirrored = orig.mAutoMirrored;
637            mThemeAttrs = orig.mThemeAttrs;
638        }
639
640        @Override
641        public boolean canApplyTheme() {
642            return mThemeAttrs != null
643                    || (mTint != null && mTint.canApplyTheme())
644                    || super.canApplyTheme();
645        }
646
647        @Override
648        public Drawable newDrawable() {
649            return new NinePatchDrawable(this, null);
650        }
651
652        @Override
653        public Drawable newDrawable(Resources res) {
654            return new NinePatchDrawable(this, res);
655        }
656
657        @Override
658        public @Config int getChangingConfigurations() {
659            return mChangingConfigurations
660                    | (mTint != null ? mTint.getChangingConfigurations() : 0);
661        }
662    }
663
664    private void computeBitmapSize() {
665        final NinePatch ninePatch = mNinePatchState.mNinePatch;
666        if (ninePatch == null) {
667            return;
668        }
669
670        final int targetDensity = mTargetDensity;
671        final int sourceDensity = ninePatch.getDensity() == Bitmap.DENSITY_NONE ?
672            targetDensity : ninePatch.getDensity();
673
674        final Insets sourceOpticalInsets = mNinePatchState.mOpticalInsets;
675        if (sourceOpticalInsets != Insets.NONE) {
676            final int left = Drawable.scaleFromDensity(
677                    sourceOpticalInsets.left, sourceDensity, targetDensity, true);
678            final int top = Drawable.scaleFromDensity(
679                    sourceOpticalInsets.top, sourceDensity, targetDensity, true);
680            final int right = Drawable.scaleFromDensity(
681                    sourceOpticalInsets.right, sourceDensity, targetDensity, true);
682            final int bottom = Drawable.scaleFromDensity(
683                    sourceOpticalInsets.bottom, sourceDensity, targetDensity, true);
684            mOpticalInsets = Insets.of(left, top, right, bottom);
685        } else {
686            mOpticalInsets = Insets.NONE;
687        }
688
689        final Rect sourcePadding = mNinePatchState.mPadding;
690        if (sourcePadding != null) {
691            if (mPadding == null) {
692                mPadding = new Rect();
693            }
694            mPadding.left = Drawable.scaleFromDensity(
695                    sourcePadding.left, sourceDensity, targetDensity, true);
696            mPadding.top = Drawable.scaleFromDensity(
697                    sourcePadding.top, sourceDensity, targetDensity, true);
698            mPadding.right = Drawable.scaleFromDensity(
699                    sourcePadding.right, sourceDensity, targetDensity, true);
700            mPadding.bottom = Drawable.scaleFromDensity(
701                    sourcePadding.bottom, sourceDensity, targetDensity, true);
702        } else {
703            mPadding = null;
704        }
705
706        mBitmapHeight = Drawable.scaleFromDensity(
707                ninePatch.getHeight(), sourceDensity, targetDensity, true);
708        mBitmapWidth = Drawable.scaleFromDensity(
709                ninePatch.getWidth(), sourceDensity, targetDensity, true);
710
711        final NinePatch.InsetStruct insets = ninePatch.getBitmap().getNinePatchInsets();
712        if (insets != null) {
713            Rect outlineRect = insets.outlineRect;
714            mOutlineInsets = NinePatch.InsetStruct.scaleInsets(outlineRect.left, outlineRect.top,
715                    outlineRect.right, outlineRect.bottom, targetDensity / (float) sourceDensity);
716            mOutlineRadius = Drawable.scaleFromDensity(
717                    insets.outlineRadius, sourceDensity, targetDensity);
718        } else {
719            mOutlineInsets = null;
720        }
721    }
722
723    /**
724     * The one constructor to rule them all. This is called by all public
725     * constructors to set the state and initialize local properties.
726     *
727     * @param state constant state to assign to the new drawable
728     */
729    private NinePatchDrawable(@NonNull NinePatchState state, @Nullable Resources res) {
730        mNinePatchState = state;
731
732        updateLocalState(res);
733    }
734
735    /**
736     * Initializes local dynamic properties from state.
737     */
738    private void updateLocalState(@Nullable Resources res) {
739        final NinePatchState state = mNinePatchState;
740
741        // If we can, avoid calling any methods that initialize Paint.
742        if (state.mDither != DEFAULT_DITHER) {
743            setDither(state.mDither);
744        }
745
746        // The nine-patch may have been created without a Resources object, in
747        // which case we should try to match the density of the nine patch (if
748        // available).
749        if (res == null && state.mNinePatch != null) {
750            mTargetDensity = state.mNinePatch.getDensity();
751        } else {
752            mTargetDensity = Drawable.resolveDensity(res, mTargetDensity);
753        }
754        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
755        computeBitmapSize();
756    }
757}
758