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