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