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.compat.annotation.UnsupportedAppUsage;
22 import android.content.pm.ActivityInfo.Config;
23 import android.content.res.ColorStateList;
24 import android.content.res.Resources;
25 import android.content.res.Resources.Theme;
26 import android.content.res.TypedArray;
27 import android.graphics.Bitmap;
28 import android.graphics.BlendMode;
29 import android.graphics.BlendModeColorFilter;
30 import android.graphics.Canvas;
31 import android.graphics.ColorFilter;
32 import android.graphics.ImageDecoder;
33 import android.graphics.Insets;
34 import android.graphics.NinePatch;
35 import android.graphics.Outline;
36 import android.graphics.Paint;
37 import android.graphics.PixelFormat;
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     @UnsupportedAppUsage
74     private NinePatchState mNinePatchState;
75     private BlendModeColorFilter mBlendModeFilter;
76     private Rect mPadding;
77     private Insets mOpticalInsets = Insets.NONE;
78     private Rect mOutlineInsets;
79     private float mOutlineRadius;
80     private Paint mPaint;
81     private boolean mMutated;
82 
83     private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
84 
85     // These are scaled to match the target density.
86     private int mBitmapWidth = -1;
87     private int mBitmapHeight = -1;
88 
NinePatchDrawable()89     NinePatchDrawable() {
90         mNinePatchState = new NinePatchState();
91     }
92 
93     /**
94      * Create drawable from raw nine-patch data, not dealing with density.
95      *
96      * @deprecated Use {@link #NinePatchDrawable(Resources, Bitmap, byte[], Rect, String)}
97      *             to ensure that the drawable has correctly set its target density.
98      */
99     @Deprecated
NinePatchDrawable(Bitmap bitmap, byte[] chunk, Rect padding, String srcName)100     public NinePatchDrawable(Bitmap bitmap, byte[] chunk, Rect padding, String srcName) {
101         this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), null);
102     }
103 
104     /**
105      * Create drawable from raw nine-patch data, setting initial target density
106      * based on the display metrics of the resources.
107      */
NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk, Rect padding, String srcName)108     public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk,
109             Rect padding, String srcName) {
110         this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), res);
111     }
112 
113     /**
114      * Create drawable from raw nine-patch data, setting initial target density
115      * based on the display metrics of the resources.
116      *
117      * @hide
118      */
NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk, Rect padding, Rect opticalInsets, String srcName)119     public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk,
120             Rect padding, Rect opticalInsets, String srcName) {
121         this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding, opticalInsets),
122                 res);
123     }
124 
125     /**
126      * Create drawable from existing nine-patch, not dealing with density.
127      *
128      * @deprecated Use {@link #NinePatchDrawable(Resources, NinePatch)}
129      *             to ensure that the drawable has correctly set its target
130      *             density.
131      */
132     @Deprecated
NinePatchDrawable(@onNull NinePatch patch)133     public NinePatchDrawable(@NonNull NinePatch patch) {
134         this(new NinePatchState(patch, new Rect()), null);
135     }
136 
137     /**
138      * Create drawable from existing nine-patch, setting initial target density
139      * based on the display metrics of the resources.
140      */
NinePatchDrawable(@ullable Resources res, @NonNull NinePatch patch)141     public NinePatchDrawable(@Nullable Resources res, @NonNull NinePatch patch) {
142         this(new NinePatchState(patch, new Rect()), res);
143     }
144 
145     /**
146      * Set the density scale at which this drawable will be rendered. This
147      * method assumes the drawable will be rendered at the same density as the
148      * specified canvas.
149      *
150      * @param canvas The Canvas from which the density scale must be obtained.
151      *
152      * @see android.graphics.Bitmap#setDensity(int)
153      * @see android.graphics.Bitmap#getDensity()
154      */
setTargetDensity(@onNull Canvas canvas)155     public void setTargetDensity(@NonNull Canvas canvas) {
156         setTargetDensity(canvas.getDensity());
157     }
158 
159     /**
160      * Set the density scale at which this drawable will be rendered.
161      *
162      * @param metrics The DisplayMetrics indicating the density scale for this drawable.
163      *
164      * @see android.graphics.Bitmap#setDensity(int)
165      * @see android.graphics.Bitmap#getDensity()
166      */
setTargetDensity(@onNull DisplayMetrics metrics)167     public void setTargetDensity(@NonNull DisplayMetrics metrics) {
168         setTargetDensity(metrics.densityDpi);
169     }
170 
171     /**
172      * Set the density at which this drawable will be rendered.
173      *
174      * @param density The density scale for this drawable.
175      *
176      * @see android.graphics.Bitmap#setDensity(int)
177      * @see android.graphics.Bitmap#getDensity()
178      */
setTargetDensity(int density)179     public void setTargetDensity(int density) {
180         if (density == 0) {
181             density = DisplayMetrics.DENSITY_DEFAULT;
182         }
183 
184         if (mTargetDensity != density) {
185             mTargetDensity = density;
186 
187             computeBitmapSize();
188             invalidateSelf();
189         }
190     }
191 
192     @Override
draw(Canvas canvas)193     public void draw(Canvas canvas) {
194         final NinePatchState state = mNinePatchState;
195 
196         Rect bounds = getBounds();
197         int restoreToCount = -1;
198 
199         final boolean clearColorFilter;
200         if (mBlendModeFilter != null && getPaint().getColorFilter() == null) {
201             mPaint.setColorFilter(mBlendModeFilter);
202             clearColorFilter = true;
203         } else {
204             clearColorFilter = false;
205         }
206 
207         final int restoreAlpha;
208         if (state.mBaseAlpha != 1.0f) {
209             restoreAlpha = getPaint().getAlpha();
210             mPaint.setAlpha((int) (restoreAlpha * state.mBaseAlpha + 0.5f));
211         } else {
212             restoreAlpha = -1;
213         }
214 
215         final boolean needsDensityScaling = canvas.getDensity() == 0
216                 && Bitmap.DENSITY_NONE != state.mNinePatch.getDensity();
217         if (needsDensityScaling) {
218             restoreToCount = restoreToCount >= 0 ? restoreToCount : canvas.save();
219 
220             // Apply density scaling.
221             final float scale = mTargetDensity / (float) state.mNinePatch.getDensity();
222             final float px = bounds.left;
223             final float py = bounds.top;
224             canvas.scale(scale, scale, px, py);
225 
226             if (mTempRect == null) {
227                 mTempRect = new Rect();
228             }
229 
230             // Scale the bounds to match.
231             final Rect scaledBounds = mTempRect;
232             scaledBounds.left = bounds.left;
233             scaledBounds.top = bounds.top;
234             scaledBounds.right = bounds.left + Math.round(bounds.width() / scale);
235             scaledBounds.bottom = bounds.top + Math.round(bounds.height() / scale);
236             bounds = scaledBounds;
237         }
238 
239         final boolean needsMirroring = needsMirroring();
240         if (needsMirroring) {
241             restoreToCount = restoreToCount >= 0 ? restoreToCount : canvas.save();
242 
243             // Mirror the 9patch.
244             final float cx = (bounds.left + bounds.right) / 2.0f;
245             final float cy = (bounds.top + bounds.bottom) / 2.0f;
246             canvas.scale(-1.0f, 1.0f, cx, cy);
247         }
248 
249         state.mNinePatch.draw(canvas, bounds, mPaint);
250 
251         if (restoreToCount >= 0) {
252             canvas.restoreToCount(restoreToCount);
253         }
254 
255         if (clearColorFilter) {
256             mPaint.setColorFilter(null);
257         }
258 
259         if (restoreAlpha >= 0) {
260             mPaint.setAlpha(restoreAlpha);
261         }
262     }
263 
264     @Override
getChangingConfigurations()265     public @Config int getChangingConfigurations() {
266         return super.getChangingConfigurations() | mNinePatchState.getChangingConfigurations();
267     }
268 
269     @Override
getPadding(@onNull Rect padding)270     public boolean getPadding(@NonNull Rect padding) {
271         if (mPadding != null) {
272             padding.set(mPadding);
273             return (padding.left | padding.top | padding.right | padding.bottom) != 0;
274         } else {
275             return super.getPadding(padding);
276         }
277     }
278 
279     @Override
getOutline(@onNull Outline outline)280     public void getOutline(@NonNull Outline outline) {
281         final Rect bounds = getBounds();
282         if (bounds.isEmpty()) {
283             return;
284         }
285 
286         if (mNinePatchState != null && mOutlineInsets != null) {
287             final NinePatch.InsetStruct insets =
288                     mNinePatchState.mNinePatch.getBitmap().getNinePatchInsets();
289             if (insets != null) {
290                 outline.setRoundRect(bounds.left + mOutlineInsets.left,
291                         bounds.top + mOutlineInsets.top,
292                         bounds.right - mOutlineInsets.right,
293                         bounds.bottom - mOutlineInsets.bottom,
294                         mOutlineRadius);
295                 outline.setAlpha(insets.outlineAlpha * (getAlpha() / 255.0f));
296                 return;
297             }
298         }
299 
300         super.getOutline(outline);
301     }
302 
303     @Override
getOpticalInsets()304     public Insets getOpticalInsets() {
305         final Insets opticalInsets = mOpticalInsets;
306         if (needsMirroring()) {
307             return Insets.of(opticalInsets.right, opticalInsets.top,
308                     opticalInsets.left, opticalInsets.bottom);
309         } else {
310             return opticalInsets;
311         }
312     }
313 
314     @Override
setAlpha(int alpha)315     public void setAlpha(int alpha) {
316         if (mPaint == null && alpha == 0xFF) {
317             // Fast common case -- leave at normal alpha.
318             return;
319         }
320         getPaint().setAlpha(alpha);
321         invalidateSelf();
322     }
323 
324     @Override
getAlpha()325     public int getAlpha() {
326         if (mPaint == null) {
327             // Fast common case -- normal alpha.
328             return 0xFF;
329         }
330         return getPaint().getAlpha();
331     }
332 
333     @Override
setColorFilter(@ullable ColorFilter colorFilter)334     public void setColorFilter(@Nullable ColorFilter colorFilter) {
335         if (mPaint == null && colorFilter == null) {
336             // Fast common case -- leave at no color filter.
337             return;
338         }
339         getPaint().setColorFilter(colorFilter);
340         invalidateSelf();
341     }
342 
343     @Override
setTintList(@ullable ColorStateList tint)344     public void setTintList(@Nullable ColorStateList tint) {
345         mNinePatchState.mTint = tint;
346         mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, tint,
347                 mNinePatchState.mBlendMode);
348         invalidateSelf();
349     }
350 
351     @Override
setTintBlendMode(@ullable BlendMode blendMode)352     public void setTintBlendMode(@Nullable BlendMode blendMode) {
353         mNinePatchState.mBlendMode = blendMode;
354         mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, mNinePatchState.mTint,
355                 blendMode);
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.mBlendMode = Drawable.parseBlendMode(tintMode, BlendMode.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.mBlendMode != null) {
571             mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, state.mTint,
572                     state.mBlendMode);
573             return true;
574         }
575 
576         return false;
577     }
578 
579     @Override
isStateful()580     public boolean isStateful() {
581         final NinePatchState s = mNinePatchState;
582         return super.isStateful() || (s.mTint != null && s.mTint.isStateful());
583     }
584 
585     /** @hide */
586     @Override
hasFocusStateSpecified()587     public boolean hasFocusStateSpecified() {
588         return mNinePatchState.mTint != null && mNinePatchState.mTint.hasFocusStateSpecified();
589     }
590 
591     final static class NinePatchState extends ConstantState {
592         @Config int mChangingConfigurations;
593 
594         // Values loaded during inflation.
595         @UnsupportedAppUsage
596         NinePatch mNinePatch = null;
597         ColorStateList mTint = null;
598         BlendMode mBlendMode = DEFAULT_BLEND_MODE;
599         Rect mPadding = null;
600         Insets mOpticalInsets = Insets.NONE;
601         float mBaseAlpha = 1.0f;
602         boolean mDither = DEFAULT_DITHER;
603         boolean mAutoMirrored = false;
604 
605         int[] mThemeAttrs;
606 
NinePatchState()607         NinePatchState() {
608             // Empty constructor.
609         }
610 
NinePatchState(@onNull NinePatch ninePatch, @Nullable Rect padding)611         NinePatchState(@NonNull NinePatch ninePatch, @Nullable Rect padding) {
612             this(ninePatch, padding, null, DEFAULT_DITHER, false);
613         }
614 
NinePatchState(@onNull NinePatch ninePatch, @Nullable Rect padding, @Nullable Rect opticalInsets)615         NinePatchState(@NonNull NinePatch ninePatch, @Nullable Rect padding,
616                 @Nullable Rect opticalInsets) {
617             this(ninePatch, padding, opticalInsets, DEFAULT_DITHER, false);
618         }
619 
NinePatchState(@onNull NinePatch ninePatch, @Nullable Rect padding, @Nullable Rect opticalInsets, boolean dither, boolean autoMirror)620         NinePatchState(@NonNull NinePatch ninePatch, @Nullable Rect padding,
621                 @Nullable Rect opticalInsets, boolean dither, boolean autoMirror) {
622             mNinePatch = ninePatch;
623             mPadding = padding;
624             mOpticalInsets = Insets.of(opticalInsets);
625             mDither = dither;
626             mAutoMirrored = autoMirror;
627         }
628 
NinePatchState(@onNull NinePatchState orig)629         NinePatchState(@NonNull NinePatchState orig) {
630             mChangingConfigurations = orig.mChangingConfigurations;
631             mNinePatch = orig.mNinePatch;
632             mTint = orig.mTint;
633             mBlendMode = orig.mBlendMode;
634             mPadding = orig.mPadding;
635             mOpticalInsets = orig.mOpticalInsets;
636             mBaseAlpha = orig.mBaseAlpha;
637             mDither = orig.mDither;
638             mAutoMirrored = orig.mAutoMirrored;
639             mThemeAttrs = orig.mThemeAttrs;
640         }
641 
642         @Override
canApplyTheme()643         public boolean canApplyTheme() {
644             return mThemeAttrs != null
645                     || (mTint != null && mTint.canApplyTheme())
646                     || super.canApplyTheme();
647         }
648 
649         @Override
newDrawable()650         public Drawable newDrawable() {
651             return new NinePatchDrawable(this, null);
652         }
653 
654         @Override
newDrawable(Resources res)655         public Drawable newDrawable(Resources res) {
656             return new NinePatchDrawable(this, res);
657         }
658 
659         @Override
getChangingConfigurations()660         public @Config int getChangingConfigurations() {
661             return mChangingConfigurations
662                     | (mTint != null ? mTint.getChangingConfigurations() : 0);
663         }
664     }
665 
computeBitmapSize()666     private void computeBitmapSize() {
667         final NinePatch ninePatch = mNinePatchState.mNinePatch;
668         if (ninePatch == null) {
669             return;
670         }
671 
672         final int targetDensity = mTargetDensity;
673         final int sourceDensity = ninePatch.getDensity() == Bitmap.DENSITY_NONE ?
674             targetDensity : ninePatch.getDensity();
675 
676         final Insets sourceOpticalInsets = mNinePatchState.mOpticalInsets;
677         if (sourceOpticalInsets != Insets.NONE) {
678             final int left = Drawable.scaleFromDensity(
679                     sourceOpticalInsets.left, sourceDensity, targetDensity, true);
680             final int top = Drawable.scaleFromDensity(
681                     sourceOpticalInsets.top, sourceDensity, targetDensity, true);
682             final int right = Drawable.scaleFromDensity(
683                     sourceOpticalInsets.right, sourceDensity, targetDensity, true);
684             final int bottom = Drawable.scaleFromDensity(
685                     sourceOpticalInsets.bottom, sourceDensity, targetDensity, true);
686             mOpticalInsets = Insets.of(left, top, right, bottom);
687         } else {
688             mOpticalInsets = Insets.NONE;
689         }
690 
691         final Rect sourcePadding = mNinePatchState.mPadding;
692         if (sourcePadding != null) {
693             if (mPadding == null) {
694                 mPadding = new Rect();
695             }
696             mPadding.left = Drawable.scaleFromDensity(
697                     sourcePadding.left, sourceDensity, targetDensity, true);
698             mPadding.top = Drawable.scaleFromDensity(
699                     sourcePadding.top, sourceDensity, targetDensity, true);
700             mPadding.right = Drawable.scaleFromDensity(
701                     sourcePadding.right, sourceDensity, targetDensity, true);
702             mPadding.bottom = Drawable.scaleFromDensity(
703                     sourcePadding.bottom, sourceDensity, targetDensity, true);
704         } else {
705             mPadding = null;
706         }
707 
708         mBitmapHeight = Drawable.scaleFromDensity(
709                 ninePatch.getHeight(), sourceDensity, targetDensity, true);
710         mBitmapWidth = Drawable.scaleFromDensity(
711                 ninePatch.getWidth(), sourceDensity, targetDensity, true);
712 
713         final NinePatch.InsetStruct insets = ninePatch.getBitmap().getNinePatchInsets();
714         if (insets != null) {
715             Rect outlineRect = insets.outlineRect;
716             mOutlineInsets = NinePatch.InsetStruct.scaleInsets(outlineRect.left, outlineRect.top,
717                     outlineRect.right, outlineRect.bottom, targetDensity / (float) sourceDensity);
718             mOutlineRadius = Drawable.scaleFromDensity(
719                     insets.outlineRadius, sourceDensity, targetDensity);
720         } else {
721             mOutlineInsets = null;
722         }
723     }
724 
725     /**
726      * The one constructor to rule them all. This is called by all public
727      * constructors to set the state and initialize local properties.
728      *
729      * @param state constant state to assign to the new drawable
730      */
NinePatchDrawable(@onNull NinePatchState state, @Nullable Resources res)731     private NinePatchDrawable(@NonNull NinePatchState state, @Nullable Resources res) {
732         mNinePatchState = state;
733 
734         updateLocalState(res);
735     }
736 
737     /**
738      * Initializes local dynamic properties from state.
739      */
updateLocalState(@ullable Resources res)740     private void updateLocalState(@Nullable Resources res) {
741         final NinePatchState state = mNinePatchState;
742 
743         // If we can, avoid calling any methods that initialize Paint.
744         if (state.mDither != DEFAULT_DITHER) {
745             setDither(state.mDither);
746         }
747 
748         // The nine-patch may have been created without a Resources object, in
749         // which case we should try to match the density of the nine patch (if
750         // available).
751         if (res == null && state.mNinePatch != null) {
752             mTargetDensity = state.mNinePatch.getDensity();
753         } else {
754             mTargetDensity = Drawable.resolveDensity(res, mTargetDensity);
755         }
756         mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, state.mTint, state.mBlendMode);
757         computeBitmapSize();
758     }
759 }
760