BitmapDrawable.java revision 50954d2b4ea938d787ef5021d75f6bc02826607a
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
17package android.graphics.drawable;
18
19import android.annotation.NonNull;
20import android.content.pm.ActivityInfo.Config;
21import android.content.res.ColorStateList;
22import android.content.res.Resources;
23import android.content.res.Resources.Theme;
24import android.content.res.TypedArray;
25import android.graphics.Bitmap;
26import android.graphics.BitmapFactory;
27import android.graphics.BitmapShader;
28import android.graphics.Canvas;
29import android.graphics.ColorFilter;
30import android.graphics.Insets;
31import android.graphics.Matrix;
32import android.graphics.Outline;
33import android.graphics.Paint;
34import android.graphics.PixelFormat;
35import android.graphics.PorterDuff;
36import android.graphics.PorterDuff.Mode;
37import android.graphics.PorterDuffColorFilter;
38import android.graphics.Rect;
39import android.graphics.Shader;
40import android.graphics.Xfermode;
41import android.util.AttributeSet;
42import android.util.DisplayMetrics;
43import android.util.LayoutDirection;
44import android.util.TypedValue;
45import android.view.Gravity;
46
47import com.android.internal.R;
48
49import org.xmlpull.v1.XmlPullParser;
50import org.xmlpull.v1.XmlPullParserException;
51
52import java.io.IOException;
53import 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 */
76public 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
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
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
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     */
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
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")
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
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")
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     */
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     */
210    public final Bitmap getBitmap() {
211        return mBitmapState.mBitmap;
212    }
213
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 */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
347    public boolean hasAntiAlias() {
348        return mBitmapState.mPaint.isAntiAlias();
349    }
350
351    @Override
352    public void setFilterBitmap(boolean filter) {
353        mBitmapState.mPaint.setFilterBitmap(filter);
354        invalidateSelf();
355    }
356
357    @Override
358    public boolean isFilterBitmap() {
359        return mBitmapState.mPaint.isFilterBitmap();
360    }
361
362    @Override
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     */
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     */
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     */
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     */
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     */
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
446    public void setAutoMirrored(boolean mirrored) {
447        if (mBitmapState.mAutoMirrored != mirrored) {
448            mBitmapState.mAutoMirrored = mirrored;
449            invalidateSelf();
450        }
451    }
452
453    @Override
454    public final boolean isAutoMirrored() {
455        return mBitmapState.mAutoMirrored;
456    }
457
458    @Override
459    public @Config int getChangingConfigurations() {
460        return super.getChangingConfigurations() | mBitmapState.getChangingConfigurations();
461    }
462
463    private boolean needMirroring() {
464        return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
465    }
466
467    @Override
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
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     */
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
586    private Matrix getOrCreateMirrorMatrix() {
587        if (mMirrorMatrix == null) {
588            mMirrorMatrix = new Matrix();
589        }
590        return mMirrorMatrix;
591    }
592
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
618    public Insets getOpticalInsets() {
619        updateDstRectAndInsetsIfDirty();
620        return mOpticalInsets;
621    }
622
623    @Override
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
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
645    public int getAlpha() {
646        return mBitmapState.mPaint.getAlpha();
647    }
648
649    @Override
650    public void setColorFilter(ColorFilter colorFilter) {
651        mBitmapState.mPaint.setColorFilter(colorFilter);
652        invalidateSelf();
653    }
654
655    @Override
656    public ColorFilter getColorFilter() {
657        return mBitmapState.mPaint.getColorFilter();
658    }
659
660    @Override
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
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     */
683    public ColorStateList getTint() {
684        return mBitmapState.mTint;
685    }
686
687    /**
688     * @hide only needed by a hack within ProgressBar
689     */
690    public Mode getTintMode() {
691        return mBitmapState.mTintMode;
692    }
693
694    /**
695     * @hide Candidate for future API inclusion
696     */
697    @Override
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
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     */
721    public void clearMutated() {
722        super.clearMutated();
723        mMutated = false;
724    }
725
726    @Override
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
737    public boolean isStateful() {
738        return (mBitmapState.mTint != null && mBitmapState.mTint.isStateful())
739                || super.isStateful();
740    }
741
742    /** @hide */
743    @Override
744    public boolean hasFocusStateSpecified() {
745        return mBitmapState.mTint != null && mBitmapState.mTint.hasFocusStateSpecified();
746    }
747
748    @Override
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     */
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     */
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
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
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
916    public boolean canApplyTheme() {
917        return mBitmapState != null && mBitmapState.canApplyTheme();
918    }
919
920    @Override
921    public int getIntrinsicWidth() {
922        return mBitmapWidth;
923    }
924
925    @Override
926    public int getIntrinsicHeight() {
927        return mBitmapHeight;
928    }
929
930    @Override
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
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
972        BitmapState(Bitmap bitmap) {
973            mBitmap = bitmap;
974            mPaint = new Paint(DEFAULT_PAINT_FLAGS);
975        }
976
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
995        public boolean canApplyTheme() {
996            return mThemeAttrs != null || mTint != null && mTint.canApplyTheme();
997        }
998
999        @Override
1000        public Drawable newDrawable() {
1001            return new BitmapDrawable(this, null);
1002        }
1003
1004        @Override
1005        public Drawable newDrawable(Resources res) {
1006            return new BitmapDrawable(this, res);
1007        }
1008
1009        @Override
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     */
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     */
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