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