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.content.res.ColorStateList;
21 import android.content.res.Resources;
22 import android.content.res.Resources.Theme;
23 import android.content.res.TypedArray;
24 import android.graphics.Bitmap;
25 import android.graphics.BitmapFactory;
26 import android.graphics.BitmapShader;
27 import android.graphics.Canvas;
28 import android.graphics.ColorFilter;
29 import android.graphics.Insets;
30 import android.graphics.Matrix;
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.Shader;
39 import android.graphics.Xfermode;
40 import android.util.AttributeSet;
41 import android.util.DisplayMetrics;
42 import android.util.LayoutDirection;
43 import android.view.Gravity;
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.util.Collection;
52 
53 /**
54  * A Drawable that wraps a bitmap and can be tiled, stretched, or aligned. You can create a
55  * BitmapDrawable from a file path, an input stream, through XML inflation, or from
56  * a {@link android.graphics.Bitmap} object.
57  * <p>It can be defined in an XML file with the <code>&lt;bitmap></code> element.  For more
58  * information, see the guide to <a
59  * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
60  * <p>
61  * Also see the {@link android.graphics.Bitmap} class, which handles the management and
62  * transformation of raw bitmap graphics, and should be used when drawing to a
63  * {@link android.graphics.Canvas}.
64  * </p>
65  *
66  * @attr ref android.R.styleable#BitmapDrawable_src
67  * @attr ref android.R.styleable#BitmapDrawable_antialias
68  * @attr ref android.R.styleable#BitmapDrawable_filter
69  * @attr ref android.R.styleable#BitmapDrawable_dither
70  * @attr ref android.R.styleable#BitmapDrawable_gravity
71  * @attr ref android.R.styleable#BitmapDrawable_mipMap
72  * @attr ref android.R.styleable#BitmapDrawable_tileMode
73  */
74 public class BitmapDrawable extends Drawable {
75     private static final int DEFAULT_PAINT_FLAGS =
76             Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG;
77 
78     // Constants for {@link android.R.styleable#BitmapDrawable_tileMode}.
79     private static final int TILE_MODE_UNDEFINED = -2;
80     private static final int TILE_MODE_DISABLED = -1;
81     private static final int TILE_MODE_CLAMP = 0;
82     private static final int TILE_MODE_REPEAT = 1;
83     private static final int TILE_MODE_MIRROR = 2;
84 
85     private final Rect mDstRect = new Rect();   // #updateDstRectAndInsetsIfDirty() sets this
86 
87     private BitmapState mBitmapState;
88     private PorterDuffColorFilter mTintFilter;
89 
90     private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
91 
92     private boolean mDstRectAndInsetsDirty = true;
93     private boolean mMutated;
94 
95      // These are scaled to match the target density.
96     private int mBitmapWidth;
97     private int mBitmapHeight;
98 
99     /** Optical insets due to gravity. */
100     private Insets mOpticalInsets = Insets.NONE;
101 
102     // Mirroring matrix for using with Shaders
103     private Matrix mMirrorMatrix;
104 
105     /**
106      * Create an empty drawable, not dealing with density.
107      * @deprecated Use {@link #BitmapDrawable(android.content.res.Resources, android.graphics.Bitmap)}
108      * instead to specify a bitmap to draw with and ensure the correct density is set.
109      */
110     @Deprecated
BitmapDrawable()111     public BitmapDrawable() {
112         mBitmapState = new BitmapState((Bitmap) null);
113     }
114 
115     /**
116      * Create an empty drawable, setting initial target density based on
117      * the display metrics of the resources.
118      *
119      * @deprecated Use {@link #BitmapDrawable(android.content.res.Resources, android.graphics.Bitmap)}
120      * instead to specify a bitmap to draw with.
121      */
122     @SuppressWarnings("unused")
123     @Deprecated
BitmapDrawable(Resources res)124     public BitmapDrawable(Resources res) {
125         mBitmapState = new BitmapState((Bitmap) null);
126         mBitmapState.mTargetDensity = mTargetDensity;
127     }
128 
129     /**
130      * Create drawable from a bitmap, not dealing with density.
131      * @deprecated Use {@link #BitmapDrawable(Resources, Bitmap)} to ensure
132      * that the drawable has correctly set its target density.
133      */
134     @Deprecated
BitmapDrawable(Bitmap bitmap)135     public BitmapDrawable(Bitmap bitmap) {
136         this(new BitmapState(bitmap), null);
137     }
138 
139     /**
140      * Create drawable from a bitmap, setting initial target density based on
141      * the display metrics of the resources.
142      */
BitmapDrawable(Resources res, Bitmap bitmap)143     public BitmapDrawable(Resources res, Bitmap bitmap) {
144         this(new BitmapState(bitmap), res);
145         mBitmapState.mTargetDensity = mTargetDensity;
146     }
147 
148     /**
149      * Create a drawable by opening a given file path and decoding the bitmap.
150      * @deprecated Use {@link #BitmapDrawable(Resources, String)} to ensure
151      * that the drawable has correctly set its target density.
152      */
153     @Deprecated
BitmapDrawable(String filepath)154     public BitmapDrawable(String filepath) {
155         this(new BitmapState(BitmapFactory.decodeFile(filepath)), null);
156         if (mBitmapState.mBitmap == null) {
157             android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
158         }
159     }
160 
161     /**
162      * Create a drawable by opening a given file path and decoding the bitmap.
163      */
164     @SuppressWarnings("unused")
BitmapDrawable(Resources res, String filepath)165     public BitmapDrawable(Resources res, String filepath) {
166         this(new BitmapState(BitmapFactory.decodeFile(filepath)), null);
167         mBitmapState.mTargetDensity = mTargetDensity;
168         if (mBitmapState.mBitmap == null) {
169             android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
170         }
171     }
172 
173     /**
174      * Create a drawable by decoding a bitmap from the given input stream.
175      * @deprecated Use {@link #BitmapDrawable(Resources, java.io.InputStream)} to ensure
176      * that the drawable has correctly set its target density.
177      */
178     @Deprecated
BitmapDrawable(java.io.InputStream is)179     public BitmapDrawable(java.io.InputStream is) {
180         this(new BitmapState(BitmapFactory.decodeStream(is)), null);
181         if (mBitmapState.mBitmap == null) {
182             android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
183         }
184     }
185 
186     /**
187      * Create a drawable by decoding a bitmap from the given input stream.
188      */
189     @SuppressWarnings("unused")
BitmapDrawable(Resources res, java.io.InputStream is)190     public BitmapDrawable(Resources res, java.io.InputStream is) {
191         this(new BitmapState(BitmapFactory.decodeStream(is)), null);
192         mBitmapState.mTargetDensity = mTargetDensity;
193         if (mBitmapState.mBitmap == null) {
194             android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
195         }
196     }
197 
198     /**
199      * Returns the paint used to render this drawable.
200      */
getPaint()201     public final Paint getPaint() {
202         return mBitmapState.mPaint;
203     }
204 
205     /**
206      * Returns the bitmap used by this drawable to render. May be null.
207      */
getBitmap()208     public final Bitmap getBitmap() {
209         return mBitmapState.mBitmap;
210     }
211 
computeBitmapSize()212     private void computeBitmapSize() {
213         final Bitmap bitmap = mBitmapState.mBitmap;
214         if (bitmap != null) {
215             mBitmapWidth = bitmap.getScaledWidth(mTargetDensity);
216             mBitmapHeight = bitmap.getScaledHeight(mTargetDensity);
217         } else {
218             mBitmapWidth = mBitmapHeight = -1;
219         }
220     }
221 
setBitmap(Bitmap bitmap)222     private void setBitmap(Bitmap bitmap) {
223         if (mBitmapState.mBitmap != bitmap) {
224             mBitmapState.mBitmap = bitmap;
225             computeBitmapSize();
226             invalidateSelf();
227         }
228     }
229 
230     /**
231      * Set the density scale at which this drawable will be rendered. This
232      * method assumes the drawable will be rendered at the same density as the
233      * specified canvas.
234      *
235      * @param canvas The Canvas from which the density scale must be obtained.
236      *
237      * @see android.graphics.Bitmap#setDensity(int)
238      * @see android.graphics.Bitmap#getDensity()
239      */
setTargetDensity(Canvas canvas)240     public void setTargetDensity(Canvas canvas) {
241         setTargetDensity(canvas.getDensity());
242     }
243 
244     /**
245      * Set the density scale at which this drawable will be rendered.
246      *
247      * @param metrics The DisplayMetrics indicating the density scale for this drawable.
248      *
249      * @see android.graphics.Bitmap#setDensity(int)
250      * @see android.graphics.Bitmap#getDensity()
251      */
setTargetDensity(DisplayMetrics metrics)252     public void setTargetDensity(DisplayMetrics metrics) {
253         setTargetDensity(metrics.densityDpi);
254     }
255 
256     /**
257      * Set the density at which this drawable will be rendered.
258      *
259      * @param density The density scale for this drawable.
260      *
261      * @see android.graphics.Bitmap#setDensity(int)
262      * @see android.graphics.Bitmap#getDensity()
263      */
setTargetDensity(int density)264     public void setTargetDensity(int density) {
265         if (mTargetDensity != density) {
266             mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density;
267             if (mBitmapState.mBitmap != null) {
268                 computeBitmapSize();
269             }
270             invalidateSelf();
271         }
272     }
273 
274     /** Get the gravity used to position/stretch the bitmap within its bounds.
275      * See android.view.Gravity
276      * @return the gravity applied to the bitmap
277      */
getGravity()278     public int getGravity() {
279         return mBitmapState.mGravity;
280     }
281 
282     /** Set the gravity used to position/stretch the bitmap within its bounds.
283         See android.view.Gravity
284      * @param gravity the gravity
285      */
setGravity(int gravity)286     public void setGravity(int gravity) {
287         if (mBitmapState.mGravity != gravity) {
288             mBitmapState.mGravity = gravity;
289             mDstRectAndInsetsDirty = true;
290             invalidateSelf();
291         }
292     }
293 
294     /**
295      * Enables or disables the mipmap hint for this drawable's bitmap.
296      * See {@link Bitmap#setHasMipMap(boolean)} for more information.
297      *
298      * If the bitmap is null calling this method has no effect.
299      *
300      * @param mipMap True if the bitmap should use mipmaps, false otherwise.
301      *
302      * @see #hasMipMap()
303      */
setMipMap(boolean mipMap)304     public void setMipMap(boolean mipMap) {
305         if (mBitmapState.mBitmap != null) {
306             mBitmapState.mBitmap.setHasMipMap(mipMap);
307             invalidateSelf();
308         }
309     }
310 
311     /**
312      * Indicates whether the mipmap hint is enabled on this drawable's bitmap.
313      *
314      * @return True if the mipmap hint is set, false otherwise. If the bitmap
315      *         is null, this method always returns false.
316      *
317      * @see #setMipMap(boolean)
318      * @attr ref android.R.styleable#BitmapDrawable_mipMap
319      */
hasMipMap()320     public boolean hasMipMap() {
321         return mBitmapState.mBitmap != null && mBitmapState.mBitmap.hasMipMap();
322     }
323 
324     /**
325      * Enables or disables anti-aliasing for this drawable. Anti-aliasing affects
326      * the edges of the bitmap only so it applies only when the drawable is rotated.
327      *
328      * @param aa True if the bitmap should be anti-aliased, false otherwise.
329      *
330      * @see #hasAntiAlias()
331      */
setAntiAlias(boolean aa)332     public void setAntiAlias(boolean aa) {
333         mBitmapState.mPaint.setAntiAlias(aa);
334         invalidateSelf();
335     }
336 
337     /**
338      * Indicates whether anti-aliasing is enabled for this drawable.
339      *
340      * @return True if anti-aliasing is enabled, false otherwise.
341      *
342      * @see #setAntiAlias(boolean)
343      */
hasAntiAlias()344     public boolean hasAntiAlias() {
345         return mBitmapState.mPaint.isAntiAlias();
346     }
347 
348     @Override
setFilterBitmap(boolean filter)349     public void setFilterBitmap(boolean filter) {
350         mBitmapState.mPaint.setFilterBitmap(filter);
351         invalidateSelf();
352     }
353 
354     @Override
setDither(boolean dither)355     public void setDither(boolean dither) {
356         mBitmapState.mPaint.setDither(dither);
357         invalidateSelf();
358     }
359 
360     /**
361      * Indicates the repeat behavior of this drawable on the X axis.
362      *
363      * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat,
364      *         {@link android.graphics.Shader.TileMode#REPEAT} or
365      *         {@link android.graphics.Shader.TileMode#MIRROR} otherwise.
366      */
getTileModeX()367     public Shader.TileMode getTileModeX() {
368         return mBitmapState.mTileModeX;
369     }
370 
371     /**
372      * Indicates the repeat behavior of this drawable on the Y axis.
373      *
374      * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat,
375      *         {@link android.graphics.Shader.TileMode#REPEAT} or
376      *         {@link android.graphics.Shader.TileMode#MIRROR} otherwise.
377      */
getTileModeY()378     public Shader.TileMode getTileModeY() {
379         return mBitmapState.mTileModeY;
380     }
381 
382     /**
383      * Sets the repeat behavior of this drawable on the X axis. By default, the drawable
384      * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
385      * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
386      * if the bitmap is smaller than this drawable.
387      *
388      * @param mode The repeat mode for this drawable.
389      *
390      * @see #setTileModeY(android.graphics.Shader.TileMode)
391      * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode)
392      * @attr ref android.R.styleable#BitmapDrawable_tileModeX
393      */
setTileModeX(Shader.TileMode mode)394     public void setTileModeX(Shader.TileMode mode) {
395         setTileModeXY(mode, mBitmapState.mTileModeY);
396     }
397 
398     /**
399      * Sets the repeat behavior of this drawable on the Y axis. By default, the drawable
400      * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
401      * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
402      * if the bitmap is smaller than this drawable.
403      *
404      * @param mode The repeat mode for this drawable.
405      *
406      * @see #setTileModeX(android.graphics.Shader.TileMode)
407      * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode)
408      * @attr ref android.R.styleable#BitmapDrawable_tileModeY
409      */
setTileModeY(Shader.TileMode mode)410     public final void setTileModeY(Shader.TileMode mode) {
411         setTileModeXY(mBitmapState.mTileModeX, mode);
412     }
413 
414     /**
415      * Sets the repeat behavior of this drawable on both axis. By default, the drawable
416      * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
417      * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
418      * if the bitmap is smaller than this drawable.
419      *
420      * @param xmode The X repeat mode for this drawable.
421      * @param ymode The Y repeat mode for this drawable.
422      *
423      * @see #setTileModeX(android.graphics.Shader.TileMode)
424      * @see #setTileModeY(android.graphics.Shader.TileMode)
425      */
setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode)426     public void setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode) {
427         final BitmapState state = mBitmapState;
428         if (state.mTileModeX != xmode || state.mTileModeY != ymode) {
429             state.mTileModeX = xmode;
430             state.mTileModeY = ymode;
431             state.mRebuildShader = true;
432             mDstRectAndInsetsDirty = true;
433             invalidateSelf();
434         }
435     }
436 
437     @Override
setAutoMirrored(boolean mirrored)438     public void setAutoMirrored(boolean mirrored) {
439         if (mBitmapState.mAutoMirrored != mirrored) {
440             mBitmapState.mAutoMirrored = mirrored;
441             invalidateSelf();
442         }
443     }
444 
445     @Override
isAutoMirrored()446     public final boolean isAutoMirrored() {
447         return mBitmapState.mAutoMirrored;
448     }
449 
450     @Override
getChangingConfigurations()451     public int getChangingConfigurations() {
452         return super.getChangingConfigurations() | mBitmapState.mChangingConfigurations;
453     }
454 
needMirroring()455     private boolean needMirroring() {
456         return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
457     }
458 
updateMirrorMatrix(float dx)459     private void updateMirrorMatrix(float dx) {
460         if (mMirrorMatrix == null) {
461             mMirrorMatrix = new Matrix();
462         }
463         mMirrorMatrix.setTranslate(dx, 0);
464         mMirrorMatrix.preScale(-1.0f, 1.0f);
465     }
466 
467     @Override
onBoundsChange(Rect bounds)468     protected void onBoundsChange(Rect bounds) {
469         mDstRectAndInsetsDirty = true;
470 
471         final Shader shader = mBitmapState.mPaint.getShader();
472         if (shader != null) {
473             if (needMirroring()) {
474                 updateMirrorMatrix(bounds.right - bounds.left);
475                 shader.setLocalMatrix(mMirrorMatrix);
476                 mBitmapState.mPaint.setShader(shader);
477             } else {
478                 if (mMirrorMatrix != null) {
479                     mMirrorMatrix = null;
480                     shader.setLocalMatrix(Matrix.IDENTITY_MATRIX);
481                     mBitmapState.mPaint.setShader(shader);
482                 }
483             }
484         }
485     }
486 
487     @Override
draw(Canvas canvas)488     public void draw(Canvas canvas) {
489         final Bitmap bitmap = mBitmapState.mBitmap;
490         if (bitmap == null) {
491             return;
492         }
493 
494         final BitmapState state = mBitmapState;
495         final Paint paint = state.mPaint;
496         if (state.mRebuildShader) {
497             final Shader.TileMode tmx = state.mTileModeX;
498             final Shader.TileMode tmy = state.mTileModeY;
499             if (tmx == null && tmy == null) {
500                 paint.setShader(null);
501             } else {
502                 paint.setShader(new BitmapShader(bitmap,
503                         tmx == null ? Shader.TileMode.CLAMP : tmx,
504                         tmy == null ? Shader.TileMode.CLAMP : tmy));
505             }
506 
507             state.mRebuildShader = false;
508         }
509 
510         final int restoreAlpha;
511         if (state.mBaseAlpha != 1.0f) {
512             final Paint p = getPaint();
513             restoreAlpha = p.getAlpha();
514             p.setAlpha((int) (restoreAlpha * state.mBaseAlpha + 0.5f));
515         } else {
516             restoreAlpha = -1;
517         }
518 
519         final boolean clearColorFilter;
520         if (mTintFilter != null && paint.getColorFilter() == null) {
521             paint.setColorFilter(mTintFilter);
522             clearColorFilter = true;
523         } else {
524             clearColorFilter = false;
525         }
526 
527         updateDstRectAndInsetsIfDirty();
528         final Shader shader = paint.getShader();
529         final boolean needMirroring = needMirroring();
530         if (shader == null) {
531             if (needMirroring) {
532                 canvas.save();
533                 // Mirror the bitmap
534                 canvas.translate(mDstRect.right - mDstRect.left, 0);
535                 canvas.scale(-1.0f, 1.0f);
536             }
537 
538             canvas.drawBitmap(bitmap, null, mDstRect, paint);
539 
540             if (needMirroring) {
541                 canvas.restore();
542             }
543         } else {
544             if (needMirroring) {
545                 // Mirror the bitmap
546                 updateMirrorMatrix(mDstRect.right - mDstRect.left);
547                 shader.setLocalMatrix(mMirrorMatrix);
548                 paint.setShader(shader);
549             } else {
550                 if (mMirrorMatrix != null) {
551                     mMirrorMatrix = null;
552                     shader.setLocalMatrix(Matrix.IDENTITY_MATRIX);
553                     paint.setShader(shader);
554                 }
555             }
556 
557             canvas.drawRect(mDstRect, paint);
558         }
559 
560         if (clearColorFilter) {
561             paint.setColorFilter(null);
562         }
563 
564         if (restoreAlpha >= 0) {
565             paint.setAlpha(restoreAlpha);
566         }
567     }
568 
updateDstRectAndInsetsIfDirty()569     private void updateDstRectAndInsetsIfDirty() {
570         if (mDstRectAndInsetsDirty) {
571             if (mBitmapState.mTileModeX == null && mBitmapState.mTileModeY == null) {
572                 final Rect bounds = getBounds();
573                 final int layoutDirection = getLayoutDirection();
574                 Gravity.apply(mBitmapState.mGravity, mBitmapWidth, mBitmapHeight,
575                         bounds, mDstRect, layoutDirection);
576 
577                 final int left = mDstRect.left - bounds.left;
578                 final int top = mDstRect.top - bounds.top;
579                 final int right = bounds.right - mDstRect.right;
580                 final int bottom = bounds.bottom - mDstRect.bottom;
581                 mOpticalInsets = Insets.of(left, top, right, bottom);
582             } else {
583                 copyBounds(mDstRect);
584                 mOpticalInsets = Insets.NONE;
585             }
586         }
587         mDstRectAndInsetsDirty = false;
588     }
589 
590     /**
591      * @hide
592      */
593     @Override
getOpticalInsets()594     public Insets getOpticalInsets() {
595         updateDstRectAndInsetsIfDirty();
596         return mOpticalInsets;
597     }
598 
599     @Override
getOutline(@onNull Outline outline)600     public void getOutline(@NonNull Outline outline) {
601         updateDstRectAndInsetsIfDirty();
602         outline.setRect(mDstRect);
603 
604         // Only opaque Bitmaps can report a non-0 alpha,
605         // since only they are guaranteed to fill their bounds
606         boolean opaqueOverShape = mBitmapState.mBitmap != null
607                 && !mBitmapState.mBitmap.hasAlpha();
608         outline.setAlpha(opaqueOverShape ? getAlpha() / 255.0f : 0.0f);
609     }
610 
611     @Override
setAlpha(int alpha)612     public void setAlpha(int alpha) {
613         final int oldAlpha = mBitmapState.mPaint.getAlpha();
614         if (alpha != oldAlpha) {
615             mBitmapState.mPaint.setAlpha(alpha);
616             invalidateSelf();
617         }
618     }
619 
620     @Override
getAlpha()621     public int getAlpha() {
622         return mBitmapState.mPaint.getAlpha();
623     }
624 
625     @Override
setColorFilter(ColorFilter cf)626     public void setColorFilter(ColorFilter cf) {
627         mBitmapState.mPaint.setColorFilter(cf);
628         invalidateSelf();
629     }
630 
631     @Override
getColorFilter()632     public ColorFilter getColorFilter() {
633         return mBitmapState.mPaint.getColorFilter();
634     }
635 
636     @Override
setTintList(ColorStateList tint)637     public void setTintList(ColorStateList tint) {
638         mBitmapState.mTint = tint;
639         mTintFilter = updateTintFilter(mTintFilter, tint, mBitmapState.mTintMode);
640         invalidateSelf();
641     }
642 
643     @Override
setTintMode(PorterDuff.Mode tintMode)644     public void setTintMode(PorterDuff.Mode tintMode) {
645         mBitmapState.mTintMode = tintMode;
646         mTintFilter = updateTintFilter(mTintFilter, mBitmapState.mTint, tintMode);
647         invalidateSelf();
648     }
649 
650     /**
651      * @hide only needed by a hack within ProgressBar
652      */
getTint()653     public ColorStateList getTint() {
654         return mBitmapState.mTint;
655     }
656 
657     /**
658      * @hide only needed by a hack within ProgressBar
659      */
getTintMode()660     public Mode getTintMode() {
661         return mBitmapState.mTintMode;
662     }
663 
664     /**
665      * @hide Candidate for future API inclusion
666      */
667     @Override
setXfermode(Xfermode xfermode)668     public void setXfermode(Xfermode xfermode) {
669         mBitmapState.mPaint.setXfermode(xfermode);
670         invalidateSelf();
671     }
672 
673     /**
674      * A mutable BitmapDrawable still shares its Bitmap with any other Drawable
675      * that comes from the same resource.
676      *
677      * @return This drawable.
678      */
679     @Override
mutate()680     public Drawable mutate() {
681         if (!mMutated && super.mutate() == this) {
682             mBitmapState = new BitmapState(mBitmapState);
683             mMutated = true;
684         }
685         return this;
686     }
687 
688     /**
689      * @hide
690      */
clearMutated()691     public void clearMutated() {
692         super.clearMutated();
693         mMutated = false;
694     }
695 
696     @Override
onStateChange(int[] stateSet)697     protected boolean onStateChange(int[] stateSet) {
698         final BitmapState state = mBitmapState;
699         if (state.mTint != null && state.mTintMode != null) {
700             mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
701             return true;
702         }
703         return false;
704     }
705 
706     @Override
isStateful()707     public boolean isStateful() {
708         final BitmapState s = mBitmapState;
709         return super.isStateful() || (s.mTint != null && s.mTint.isStateful());
710     }
711 
712     @Override
inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)713     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
714             throws XmlPullParserException, IOException {
715         super.inflate(r, parser, attrs, theme);
716 
717         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.BitmapDrawable);
718         updateStateFromTypedArray(a);
719         verifyState(a);
720         a.recycle();
721     }
722 
723     /**
724      * Ensures all required attributes are set.
725      *
726      * @throws XmlPullParserException if any required attributes are missing
727      */
verifyState(TypedArray a)728     private void verifyState(TypedArray a) throws XmlPullParserException {
729         final BitmapState state = mBitmapState;
730         if (state.mBitmap == null) {
731             throw new XmlPullParserException(a.getPositionDescription() +
732                     ": <bitmap> requires a valid src attribute");
733         }
734     }
735 
736     /**
737      * Updates the constant state from the values in the typed array.
738      */
updateStateFromTypedArray(TypedArray a)739     private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
740         final Resources r = a.getResources();
741         final BitmapState state = mBitmapState;
742 
743         // Account for any configuration changes.
744         state.mChangingConfigurations |= a.getChangingConfigurations();
745 
746         // Extract the theme attributes, if any.
747         state.mThemeAttrs = a.extractThemeAttrs();
748 
749         final int srcResId = a.getResourceId(R.styleable.BitmapDrawable_src, 0);
750         if (srcResId != 0) {
751             final Bitmap bitmap = BitmapFactory.decodeResource(r, srcResId);
752             if (bitmap == null) {
753                 throw new XmlPullParserException(a.getPositionDescription() +
754                         ": <bitmap> requires a valid src attribute");
755             }
756 
757             state.mBitmap = bitmap;
758         }
759 
760         state.mTargetDensity = r.getDisplayMetrics().densityDpi;
761 
762         final boolean defMipMap = state.mBitmap != null ? state.mBitmap.hasMipMap() : false;
763         setMipMap(a.getBoolean(R.styleable.BitmapDrawable_mipMap, defMipMap));
764 
765         state.mAutoMirrored = a.getBoolean(
766                 R.styleable.BitmapDrawable_autoMirrored, state.mAutoMirrored);
767         state.mBaseAlpha = a.getFloat(R.styleable.BitmapDrawable_alpha, state.mBaseAlpha);
768 
769         final int tintMode = a.getInt(R.styleable.BitmapDrawable_tintMode, -1);
770         if (tintMode != -1) {
771             state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
772         }
773 
774         final ColorStateList tint = a.getColorStateList(R.styleable.BitmapDrawable_tint);
775         if (tint != null) {
776             state.mTint = tint;
777         }
778 
779         final Paint paint = mBitmapState.mPaint;
780         paint.setAntiAlias(a.getBoolean(
781                 R.styleable.BitmapDrawable_antialias, paint.isAntiAlias()));
782         paint.setFilterBitmap(a.getBoolean(
783                 R.styleable.BitmapDrawable_filter, paint.isFilterBitmap()));
784         paint.setDither(a.getBoolean(R.styleable.BitmapDrawable_dither, paint.isDither()));
785 
786         setGravity(a.getInt(R.styleable.BitmapDrawable_gravity, state.mGravity));
787 
788         final int tileMode = a.getInt(R.styleable.BitmapDrawable_tileMode, TILE_MODE_UNDEFINED);
789         if (tileMode != TILE_MODE_UNDEFINED) {
790             final Shader.TileMode mode = parseTileMode(tileMode);
791             setTileModeXY(mode, mode);
792         }
793 
794         final int tileModeX = a.getInt(R.styleable.BitmapDrawable_tileModeX, TILE_MODE_UNDEFINED);
795         if (tileModeX != TILE_MODE_UNDEFINED) {
796             setTileModeX(parseTileMode(tileModeX));
797         }
798 
799         final int tileModeY = a.getInt(R.styleable.BitmapDrawable_tileModeY, TILE_MODE_UNDEFINED);
800         if (tileModeY != TILE_MODE_UNDEFINED) {
801             setTileModeY(parseTileMode(tileModeY));
802         }
803 
804         // Update local properties.
805         initializeWithState(state, r);
806     }
807 
808     @Override
applyTheme(Theme t)809     public void applyTheme(Theme t) {
810         super.applyTheme(t);
811 
812         final BitmapState state = mBitmapState;
813         if (state == null || state.mThemeAttrs == null) {
814             return;
815         }
816 
817         final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.BitmapDrawable);
818         try {
819             updateStateFromTypedArray(a);
820         } catch (XmlPullParserException e) {
821             throw new RuntimeException(e);
822         } finally {
823             a.recycle();
824         }
825     }
826 
parseTileMode(int tileMode)827     private static Shader.TileMode parseTileMode(int tileMode) {
828         switch (tileMode) {
829             case TILE_MODE_CLAMP:
830                 return Shader.TileMode.CLAMP;
831             case TILE_MODE_REPEAT:
832                 return Shader.TileMode.REPEAT;
833             case TILE_MODE_MIRROR:
834                 return Shader.TileMode.MIRROR;
835             default:
836                 return null;
837         }
838     }
839 
840     @Override
canApplyTheme()841     public boolean canApplyTheme() {
842         return mBitmapState != null && mBitmapState.mThemeAttrs != null;
843     }
844 
845     @Override
getIntrinsicWidth()846     public int getIntrinsicWidth() {
847         return mBitmapWidth;
848     }
849 
850     @Override
getIntrinsicHeight()851     public int getIntrinsicHeight() {
852         return mBitmapHeight;
853     }
854 
855     @Override
getOpacity()856     public int getOpacity() {
857         if (mBitmapState.mGravity != Gravity.FILL) {
858             return PixelFormat.TRANSLUCENT;
859         }
860 
861         final Bitmap bitmap = mBitmapState.mBitmap;
862         return (bitmap == null || bitmap.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ?
863                 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
864     }
865 
866     @Override
getConstantState()867     public final ConstantState getConstantState() {
868         mBitmapState.mChangingConfigurations = getChangingConfigurations();
869         return mBitmapState;
870     }
871 
872     final static class BitmapState extends ConstantState {
873         final Paint mPaint;
874 
875         // Values loaded during inflation.
876         int[] mThemeAttrs = null;
877         Bitmap mBitmap = null;
878         ColorStateList mTint = null;
879         Mode mTintMode = DEFAULT_TINT_MODE;
880         int mGravity = Gravity.FILL;
881         float mBaseAlpha = 1.0f;
882         Shader.TileMode mTileModeX = null;
883         Shader.TileMode mTileModeY = null;
884         int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
885         boolean mAutoMirrored = false;
886 
887         int mChangingConfigurations;
888         boolean mRebuildShader;
889 
BitmapState(Bitmap bitmap)890         BitmapState(Bitmap bitmap) {
891             mBitmap = bitmap;
892             mPaint = new Paint(DEFAULT_PAINT_FLAGS);
893         }
894 
BitmapState(BitmapState bitmapState)895         BitmapState(BitmapState bitmapState) {
896             mBitmap = bitmapState.mBitmap;
897             mTint = bitmapState.mTint;
898             mTintMode = bitmapState.mTintMode;
899             mThemeAttrs = bitmapState.mThemeAttrs;
900             mChangingConfigurations = bitmapState.mChangingConfigurations;
901             mGravity = bitmapState.mGravity;
902             mTileModeX = bitmapState.mTileModeX;
903             mTileModeY = bitmapState.mTileModeY;
904             mTargetDensity = bitmapState.mTargetDensity;
905             mBaseAlpha = bitmapState.mBaseAlpha;
906             mPaint = new Paint(bitmapState.mPaint);
907             mRebuildShader = bitmapState.mRebuildShader;
908             mAutoMirrored = bitmapState.mAutoMirrored;
909         }
910 
911         @Override
canApplyTheme()912         public boolean canApplyTheme() {
913             return mThemeAttrs != null;
914         }
915 
916         @Override
addAtlasableBitmaps(Collection<Bitmap> atlasList)917         public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
918             if (isAtlasable(mBitmap) && atlasList.add(mBitmap)) {
919                 return mBitmap.getWidth() * mBitmap.getHeight();
920             }
921             return 0;
922         }
923 
924         @Override
newDrawable()925         public Drawable newDrawable() {
926             return new BitmapDrawable(this, null);
927         }
928 
929         @Override
newDrawable(Resources res)930         public Drawable newDrawable(Resources res) {
931             return new BitmapDrawable(this, res);
932         }
933 
934         @Override
getChangingConfigurations()935         public int getChangingConfigurations() {
936             return mChangingConfigurations;
937         }
938     }
939 
940     /**
941      * The one constructor to rule them all. This is called by all public
942      * constructors to set the state and initialize local properties.
943      */
BitmapDrawable(BitmapState state, Resources res)944     private BitmapDrawable(BitmapState state, Resources res) {
945         mBitmapState = state;
946 
947         initializeWithState(mBitmapState, res);
948     }
949 
950     /**
951      * Initializes local dynamic properties from state. This should be called
952      * after significant state changes, e.g. from the One True Constructor and
953      * after inflating or applying a theme.
954      */
initializeWithState(BitmapState state, Resources res)955     private void initializeWithState(BitmapState state, Resources res) {
956         if (res != null) {
957             mTargetDensity = res.getDisplayMetrics().densityDpi;
958         } else {
959             mTargetDensity = state.mTargetDensity;
960         }
961 
962         mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
963         computeBitmapSize();
964     }
965 }
966