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 for use by android.graphics.ImageDecoder, but must not be used outside the module.
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     @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         @UnsupportedAppUsage
595         NinePatch mNinePatch = null;
596         ColorStateList mTint = null;
597         BlendMode mBlendMode = DEFAULT_BLEND_MODE;
598         Rect mPadding = null;
599         Insets mOpticalInsets = Insets.NONE;
600         float mBaseAlpha = 1.0f;
601         boolean mDither = DEFAULT_DITHER;
602         boolean mAutoMirrored = false;
603 
604         int[] mThemeAttrs;
605 
NinePatchState()606         NinePatchState() {
607             // Empty constructor.
608         }
609 
NinePatchState(@onNull NinePatch ninePatch, @Nullable Rect padding)610         NinePatchState(@NonNull NinePatch ninePatch, @Nullable Rect padding) {
611             this(ninePatch, padding, null, DEFAULT_DITHER, false);
612         }
613 
NinePatchState(@onNull NinePatch ninePatch, @Nullable Rect padding, @Nullable Rect opticalInsets)614         NinePatchState(@NonNull NinePatch ninePatch, @Nullable Rect padding,
615                 @Nullable Rect opticalInsets) {
616             this(ninePatch, padding, opticalInsets, DEFAULT_DITHER, false);
617         }
618 
NinePatchState(@onNull NinePatch ninePatch, @Nullable Rect padding, @Nullable Rect opticalInsets, boolean dither, boolean autoMirror)619         NinePatchState(@NonNull NinePatch ninePatch, @Nullable Rect padding,
620                 @Nullable Rect opticalInsets, boolean dither, boolean autoMirror) {
621             mNinePatch = ninePatch;
622             mPadding = padding;
623             mOpticalInsets = Insets.of(opticalInsets);
624             mDither = dither;
625             mAutoMirrored = autoMirror;
626         }
627 
NinePatchState(@onNull NinePatchState orig)628         NinePatchState(@NonNull NinePatchState orig) {
629             mChangingConfigurations = orig.mChangingConfigurations;
630             mNinePatch = orig.mNinePatch;
631             mTint = orig.mTint;
632             mBlendMode = orig.mBlendMode;
633             mPadding = orig.mPadding;
634             mOpticalInsets = orig.mOpticalInsets;
635             mBaseAlpha = orig.mBaseAlpha;
636             mDither = orig.mDither;
637             mAutoMirrored = orig.mAutoMirrored;
638             mThemeAttrs = orig.mThemeAttrs;
639         }
640 
641         @Override
canApplyTheme()642         public boolean canApplyTheme() {
643             return mThemeAttrs != null
644                     || (mTint != null && mTint.canApplyTheme())
645                     || super.canApplyTheme();
646         }
647 
648         @Override
newDrawable()649         public Drawable newDrawable() {
650             return new NinePatchDrawable(this, null);
651         }
652 
653         @Override
newDrawable(Resources res)654         public Drawable newDrawable(Resources res) {
655             return new NinePatchDrawable(this, res);
656         }
657 
658         @Override
getChangingConfigurations()659         public @Config int getChangingConfigurations() {
660             return mChangingConfigurations
661                     | (mTint != null ? mTint.getChangingConfigurations() : 0);
662         }
663     }
664 
computeBitmapSize()665     private void computeBitmapSize() {
666         final NinePatch ninePatch = mNinePatchState.mNinePatch;
667         if (ninePatch == null) {
668             return;
669         }
670 
671         final int targetDensity = mTargetDensity;
672         final int sourceDensity = ninePatch.getDensity() == Bitmap.DENSITY_NONE ?
673             targetDensity : ninePatch.getDensity();
674 
675         final Insets sourceOpticalInsets = mNinePatchState.mOpticalInsets;
676         if (sourceOpticalInsets != Insets.NONE) {
677             final int left = Drawable.scaleFromDensity(
678                     sourceOpticalInsets.left, sourceDensity, targetDensity, true);
679             final int top = Drawable.scaleFromDensity(
680                     sourceOpticalInsets.top, sourceDensity, targetDensity, true);
681             final int right = Drawable.scaleFromDensity(
682                     sourceOpticalInsets.right, sourceDensity, targetDensity, true);
683             final int bottom = Drawable.scaleFromDensity(
684                     sourceOpticalInsets.bottom, sourceDensity, targetDensity, true);
685             mOpticalInsets = Insets.of(left, top, right, bottom);
686         } else {
687             mOpticalInsets = Insets.NONE;
688         }
689 
690         final Rect sourcePadding = mNinePatchState.mPadding;
691         if (sourcePadding != null) {
692             if (mPadding == null) {
693                 mPadding = new Rect();
694             }
695             mPadding.left = Drawable.scaleFromDensity(
696                     sourcePadding.left, sourceDensity, targetDensity, true);
697             mPadding.top = Drawable.scaleFromDensity(
698                     sourcePadding.top, sourceDensity, targetDensity, true);
699             mPadding.right = Drawable.scaleFromDensity(
700                     sourcePadding.right, sourceDensity, targetDensity, true);
701             mPadding.bottom = Drawable.scaleFromDensity(
702                     sourcePadding.bottom, sourceDensity, targetDensity, true);
703         } else {
704             mPadding = null;
705         }
706 
707         mBitmapHeight = Drawable.scaleFromDensity(
708                 ninePatch.getHeight(), sourceDensity, targetDensity, true);
709         mBitmapWidth = Drawable.scaleFromDensity(
710                 ninePatch.getWidth(), sourceDensity, targetDensity, true);
711 
712         final NinePatch.InsetStruct insets = ninePatch.getBitmap().getNinePatchInsets();
713         if (insets != null) {
714             Rect outlineRect = insets.outlineRect;
715             mOutlineInsets = NinePatch.InsetStruct.scaleInsets(outlineRect.left, outlineRect.top,
716                     outlineRect.right, outlineRect.bottom, targetDensity / (float) sourceDensity);
717             mOutlineRadius = Drawable.scaleFromDensity(
718                     insets.outlineRadius, sourceDensity, targetDensity);
719         } else {
720             mOutlineInsets = null;
721         }
722     }
723 
724     /**
725      * The one constructor to rule them all. This is called by all public
726      * constructors to set the state and initialize local properties.
727      *
728      * @param state constant state to assign to the new drawable
729      */
NinePatchDrawable(@onNull NinePatchState state, @Nullable Resources res)730     private NinePatchDrawable(@NonNull NinePatchState state, @Nullable Resources res) {
731         mNinePatchState = state;
732 
733         updateLocalState(res);
734     }
735 
736     /**
737      * Initializes local dynamic properties from state.
738      */
updateLocalState(@ullable Resources res)739     private void updateLocalState(@Nullable Resources res) {
740         final NinePatchState state = mNinePatchState;
741 
742         // If we can, avoid calling any methods that initialize Paint.
743         if (state.mDither != DEFAULT_DITHER) {
744             setDither(state.mDither);
745         }
746 
747         // The nine-patch may have been created without a Resources object, in
748         // which case we should try to match the density of the nine patch (if
749         // available).
750         if (res == null && state.mNinePatch != null) {
751             mTargetDensity = state.mNinePatch.getDensity();
752         } else {
753             mTargetDensity = Drawable.resolveDensity(res, mTargetDensity);
754         }
755         mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, state.mTint, state.mBlendMode);
756         computeBitmapSize();
757     }
758 }
759