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 
17 package android.graphics.drawable;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.pm.ActivityInfo.Config;
22 import android.content.res.ColorStateList;
23 import android.content.res.Resources;
24 import android.content.res.Resources.Theme;
25 import android.content.res.TypedArray;
26 import android.graphics.Bitmap;
27 import android.graphics.Canvas;
28 import android.graphics.ColorFilter;
29 import android.graphics.ImageDecoder;
30 import android.graphics.Insets;
31 import android.graphics.NinePatch;
32 import android.graphics.Outline;
33 import android.graphics.Paint;
34 import android.graphics.PixelFormat;
35 import android.graphics.PorterDuff;
36 import android.graphics.PorterDuff.Mode;
37 import android.graphics.PorterDuffColorFilter;
38 import android.graphics.Rect;
39 import android.graphics.Region;
40 import android.util.AttributeSet;
41 import android.util.DisplayMetrics;
42 import android.util.LayoutDirection;
43 import android.util.TypedValue;
44 
45 import com.android.internal.R;
46 
47 import org.xmlpull.v1.XmlPullParser;
48 import org.xmlpull.v1.XmlPullParserException;
49 
50 import java.io.IOException;
51 import 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  */
66 public 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 
NinePatchDrawable()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
NinePatchDrawable(Bitmap bitmap, byte[] chunk, Rect padding, String srcName)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      */
NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk, Rect padding, String srcName)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      */
NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk, Rect padding, Rect opticalInsets, String srcName)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
NinePatchDrawable(@onNull NinePatch patch)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      */
NinePatchDrawable(@ullable Resources res, @NonNull NinePatch patch)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      */
setTargetDensity(@onNull Canvas canvas)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      */
setTargetDensity(@onNull DisplayMetrics metrics)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      */
setTargetDensity(int density)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
draw(Canvas canvas)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
getChangingConfigurations()264     public @Config int getChangingConfigurations() {
265         return super.getChangingConfigurations() | mNinePatchState.getChangingConfigurations();
266     }
267 
268     @Override
getPadding(@onNull Rect padding)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
getOutline(@onNull Outline outline)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
getOpticalInsets()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
setAlpha(int alpha)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
getAlpha()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
setColorFilter(@ullable ColorFilter colorFilter)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
setTintList(@ullable ColorStateList tint)346     public void setTintList(@Nullable ColorStateList tint) {
347         mNinePatchState.mTint = tint;
348         mTintFilter = updateTintFilter(mTintFilter, tint, mNinePatchState.mTintMode);
349         invalidateSelf();
350     }
351 
352     @Override
setTintMode(@ullable PorterDuff.Mode tintMode)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
setDither(boolean dither)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
setAutoMirrored(boolean mirrored)372     public void setAutoMirrored(boolean mirrored) {
373         mNinePatchState.mAutoMirrored = mirrored;
374     }
375 
needsMirroring()376     private boolean needsMirroring() {
377         return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
378     }
379 
380     @Override
isAutoMirrored()381     public boolean isAutoMirrored() {
382         return mNinePatchState.mAutoMirrored;
383     }
384 
385     @Override
setFilterBitmap(boolean filter)386     public void setFilterBitmap(boolean filter) {
387         getPaint().setFilterBitmap(filter);
388         invalidateSelf();
389     }
390 
391     @Override
isFilterBitmap()392     public boolean isFilterBitmap() {
393         return mPaint != null && getPaint().isFilterBitmap();
394     }
395 
396     @Override
inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)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      */
updateStateFromTypedArray(@onNull TypedArray a)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
applyTheme(@onNull Theme t)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
canApplyTheme()509     public boolean canApplyTheme() {
510         return mNinePatchState != null && mNinePatchState.canApplyTheme();
511     }
512 
513     @NonNull
getPaint()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
getIntrinsicWidth()523     public int getIntrinsicWidth() {
524         return mBitmapWidth;
525     }
526 
527     @Override
getIntrinsicHeight()528     public int getIntrinsicHeight() {
529         return mBitmapHeight;
530     }
531 
532     @Override
getOpacity()533     public int getOpacity() {
534         return mNinePatchState.mNinePatch.hasAlpha()
535                 || (mPaint != null && mPaint.getAlpha() < 255) ?
536                         PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
537     }
538 
539     @Override
getTransparentRegion()540     public Region getTransparentRegion() {
541         return mNinePatchState.mNinePatch.getTransparentRegion(getBounds());
542     }
543 
544     @Override
getConstantState()545     public ConstantState getConstantState() {
546         mNinePatchState.mChangingConfigurations = getChangingConfigurations();
547         return mNinePatchState;
548     }
549 
550     @Override
mutate()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      */
clearMutated()562     public void clearMutated() {
563         super.clearMutated();
564         mMutated = false;
565     }
566 
567     @Override
onStateChange(int[] stateSet)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
isStateful()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
hasFocusStateSpecified()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 
NinePatchState()605         NinePatchState() {
606             // Empty constructor.
607         }
608 
NinePatchState(@onNull NinePatch ninePatch, @Nullable Rect padding)609         NinePatchState(@NonNull NinePatch ninePatch, @Nullable Rect padding) {
610             this(ninePatch, padding, null, DEFAULT_DITHER, false);
611         }
612 
NinePatchState(@onNull NinePatch ninePatch, @Nullable Rect padding, @Nullable Rect opticalInsets)613         NinePatchState(@NonNull NinePatch ninePatch, @Nullable Rect padding,
614                 @Nullable Rect opticalInsets) {
615             this(ninePatch, padding, opticalInsets, DEFAULT_DITHER, false);
616         }
617 
NinePatchState(@onNull NinePatch ninePatch, @Nullable Rect padding, @Nullable Rect opticalInsets, boolean dither, boolean autoMirror)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 
NinePatchState(@onNull NinePatchState orig)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
canApplyTheme()641         public boolean canApplyTheme() {
642             return mThemeAttrs != null
643                     || (mTint != null && mTint.canApplyTheme())
644                     || super.canApplyTheme();
645         }
646 
647         @Override
newDrawable()648         public Drawable newDrawable() {
649             return new NinePatchDrawable(this, null);
650         }
651 
652         @Override
newDrawable(Resources res)653         public Drawable newDrawable(Resources res) {
654             return new NinePatchDrawable(this, res);
655         }
656 
657         @Override
getChangingConfigurations()658         public @Config int getChangingConfigurations() {
659             return mChangingConfigurations
660                     | (mTint != null ? mTint.getChangingConfigurations() : 0);
661         }
662     }
663 
computeBitmapSize()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      */
NinePatchDrawable(@onNull NinePatchState state, @Nullable Resources res)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      */
updateLocalState(@ullable Resources res)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