BitmapDrawable.java revision 0d2a46b7336b6d255f202b878003be59ecbae52b
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.view.Gravity;
45
46import com.android.internal.R;
47
48import org.xmlpull.v1.XmlPullParser;
49import org.xmlpull.v1.XmlPullParserException;
50
51import java.io.IOException;
52import 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 */
75public 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
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
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
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     */
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
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")
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
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")
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     */
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     */
209    public final Bitmap getBitmap() {
210        return mBitmapState.mBitmap;
211    }
212
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 */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
346    public boolean hasAntiAlias() {
347        return mBitmapState.mPaint.isAntiAlias();
348    }
349
350    @Override
351    public void setFilterBitmap(boolean filter) {
352        mBitmapState.mPaint.setFilterBitmap(filter);
353        invalidateSelf();
354    }
355
356    @Override
357    public boolean isFilterBitmap() {
358        return mBitmapState.mPaint.isFilterBitmap();
359    }
360
361    @Override
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     */
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     */
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     */
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     */
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     */
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
445    public void setAutoMirrored(boolean mirrored) {
446        if (mBitmapState.mAutoMirrored != mirrored) {
447            mBitmapState.mAutoMirrored = mirrored;
448            invalidateSelf();
449        }
450    }
451
452    @Override
453    public final boolean isAutoMirrored() {
454        return mBitmapState.mAutoMirrored;
455    }
456
457    @Override
458    public @Config int getChangingConfigurations() {
459        return super.getChangingConfigurations() | mBitmapState.getChangingConfigurations();
460    }
461
462    private boolean needMirroring() {
463        return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
464    }
465
466    @Override
467    protected void onBoundsChange(Rect bounds) {
468        mDstRectAndInsetsDirty = true;
469
470        final Bitmap bitmap = mBitmapState.mBitmap;
471        final Shader shader = mBitmapState.mPaint.getShader();
472        if (bitmap != null && shader != null) {
473            updateShaderMatrix(bitmap, mBitmapState.mPaint, shader, needMirroring());
474        }
475    }
476
477    @Override
478    public void draw(Canvas canvas) {
479        final Bitmap bitmap = mBitmapState.mBitmap;
480        if (bitmap == null) {
481            return;
482        }
483
484        final BitmapState state = mBitmapState;
485        final Paint paint = state.mPaint;
486        if (state.mRebuildShader) {
487            final Shader.TileMode tmx = state.mTileModeX;
488            final Shader.TileMode tmy = state.mTileModeY;
489            if (tmx == null && tmy == null) {
490                paint.setShader(null);
491            } else {
492                paint.setShader(new BitmapShader(bitmap,
493                        tmx == null ? Shader.TileMode.CLAMP : tmx,
494                        tmy == null ? Shader.TileMode.CLAMP : tmy));
495            }
496
497            state.mRebuildShader = false;
498        }
499
500        final int restoreAlpha;
501        if (state.mBaseAlpha != 1.0f) {
502            final Paint p = getPaint();
503            restoreAlpha = p.getAlpha();
504            p.setAlpha((int) (restoreAlpha * state.mBaseAlpha + 0.5f));
505        } else {
506            restoreAlpha = -1;
507        }
508
509        final boolean clearColorFilter;
510        if (mTintFilter != null && paint.getColorFilter() == null) {
511            paint.setColorFilter(mTintFilter);
512            clearColorFilter = true;
513        } else {
514            clearColorFilter = false;
515        }
516
517        updateDstRectAndInsetsIfDirty();
518        final Shader shader = paint.getShader();
519        final boolean needMirroring = needMirroring();
520        if (shader == null) {
521            if (needMirroring) {
522                canvas.save();
523                // Mirror the bitmap
524                canvas.translate(mDstRect.right - mDstRect.left, 0);
525                canvas.scale(-1.0f, 1.0f);
526            }
527
528            canvas.drawBitmap(bitmap, null, mDstRect, paint);
529
530            if (needMirroring) {
531                canvas.restore();
532            }
533        } else {
534            updateShaderMatrix(bitmap, paint, shader, needMirroring);
535            canvas.drawRect(mDstRect, paint);
536        }
537
538        if (clearColorFilter) {
539            paint.setColorFilter(null);
540        }
541
542        if (restoreAlpha >= 0) {
543            paint.setAlpha(restoreAlpha);
544        }
545    }
546
547    /**
548     * Updates the {@code paint}'s shader matrix to be consistent with the
549     * destination size and layout direction.
550     *
551     * @param bitmap the bitmap to be drawn
552     * @param paint the paint used to draw the bitmap
553     * @param shader the shader to set on the paint
554     * @param needMirroring whether the bitmap should be mirrored
555     */
556    private void updateShaderMatrix(@NonNull Bitmap bitmap, @NonNull Paint paint,
557            @NonNull Shader shader, boolean needMirroring) {
558        final int sourceDensity = bitmap.getDensity();
559        final int targetDensity = mTargetDensity;
560        final boolean needScaling = sourceDensity != 0 && sourceDensity != targetDensity;
561        if (needScaling || needMirroring) {
562            final Matrix matrix = getOrCreateMirrorMatrix();
563            matrix.reset();
564
565            if (needMirroring) {
566                final int dx = mDstRect.right - mDstRect.left;
567                matrix.setTranslate(dx, 0);
568                matrix.setScale(-1, 1);
569            }
570
571            if (needScaling) {
572                final float densityScale = targetDensity / (float) sourceDensity;
573                matrix.postScale(densityScale, densityScale);
574            }
575
576            shader.setLocalMatrix(matrix);
577        } else {
578            mMirrorMatrix = null;
579            shader.setLocalMatrix(Matrix.IDENTITY_MATRIX);
580        }
581
582        paint.setShader(shader);
583    }
584
585    private Matrix getOrCreateMirrorMatrix() {
586        if (mMirrorMatrix == null) {
587            mMirrorMatrix = new Matrix();
588        }
589        return mMirrorMatrix;
590    }
591
592    private void updateDstRectAndInsetsIfDirty() {
593        if (mDstRectAndInsetsDirty) {
594            if (mBitmapState.mTileModeX == null && mBitmapState.mTileModeY == null) {
595                final Rect bounds = getBounds();
596                final int layoutDirection = getLayoutDirection();
597                Gravity.apply(mBitmapState.mGravity, mBitmapWidth, mBitmapHeight,
598                        bounds, mDstRect, layoutDirection);
599
600                final int left = mDstRect.left - bounds.left;
601                final int top = mDstRect.top - bounds.top;
602                final int right = bounds.right - mDstRect.right;
603                final int bottom = bounds.bottom - mDstRect.bottom;
604                mOpticalInsets = Insets.of(left, top, right, bottom);
605            } else {
606                copyBounds(mDstRect);
607                mOpticalInsets = Insets.NONE;
608            }
609        }
610        mDstRectAndInsetsDirty = false;
611    }
612
613    /**
614     * @hide
615     */
616    @Override
617    public Insets getOpticalInsets() {
618        updateDstRectAndInsetsIfDirty();
619        return mOpticalInsets;
620    }
621
622    @Override
623    public void getOutline(@NonNull Outline outline) {
624        updateDstRectAndInsetsIfDirty();
625        outline.setRect(mDstRect);
626
627        // Only opaque Bitmaps can report a non-0 alpha,
628        // since only they are guaranteed to fill their bounds
629        boolean opaqueOverShape = mBitmapState.mBitmap != null
630                && !mBitmapState.mBitmap.hasAlpha();
631        outline.setAlpha(opaqueOverShape ? getAlpha() / 255.0f : 0.0f);
632    }
633
634    @Override
635    public void setAlpha(int alpha) {
636        final int oldAlpha = mBitmapState.mPaint.getAlpha();
637        if (alpha != oldAlpha) {
638            mBitmapState.mPaint.setAlpha(alpha);
639            invalidateSelf();
640        }
641    }
642
643    @Override
644    public int getAlpha() {
645        return mBitmapState.mPaint.getAlpha();
646    }
647
648    @Override
649    public void setColorFilter(ColorFilter colorFilter) {
650        mBitmapState.mPaint.setColorFilter(colorFilter);
651        invalidateSelf();
652    }
653
654    @Override
655    public ColorFilter getColorFilter() {
656        return mBitmapState.mPaint.getColorFilter();
657    }
658
659    @Override
660    public void setTintList(ColorStateList tint) {
661        final BitmapState state = mBitmapState;
662        if (state.mTint != tint) {
663            state.mTint = tint;
664            mTintFilter = updateTintFilter(mTintFilter, tint, mBitmapState.mTintMode);
665            invalidateSelf();
666        }
667    }
668
669    @Override
670    public void setTintMode(PorterDuff.Mode tintMode) {
671        final BitmapState state = mBitmapState;
672        if (state.mTintMode != tintMode) {
673            state.mTintMode = tintMode;
674            mTintFilter = updateTintFilter(mTintFilter, mBitmapState.mTint, tintMode);
675            invalidateSelf();
676        }
677    }
678
679    /**
680     * @hide only needed by a hack within ProgressBar
681     */
682    public ColorStateList getTint() {
683        return mBitmapState.mTint;
684    }
685
686    /**
687     * @hide only needed by a hack within ProgressBar
688     */
689    public Mode getTintMode() {
690        return mBitmapState.mTintMode;
691    }
692
693    /**
694     * @hide Candidate for future API inclusion
695     */
696    @Override
697    public void setXfermode(Xfermode xfermode) {
698        mBitmapState.mPaint.setXfermode(xfermode);
699        invalidateSelf();
700    }
701
702    /**
703     * A mutable BitmapDrawable still shares its Bitmap with any other Drawable
704     * that comes from the same resource.
705     *
706     * @return This drawable.
707     */
708    @Override
709    public Drawable mutate() {
710        if (!mMutated && super.mutate() == this) {
711            mBitmapState = new BitmapState(mBitmapState);
712            mMutated = true;
713        }
714        return this;
715    }
716
717    /**
718     * @hide
719     */
720    public void clearMutated() {
721        super.clearMutated();
722        mMutated = false;
723    }
724
725    @Override
726    protected boolean onStateChange(int[] stateSet) {
727        final BitmapState state = mBitmapState;
728        if (state.mTint != null && state.mTintMode != null) {
729            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
730            return true;
731        }
732        return false;
733    }
734
735    @Override
736    public boolean isStateful() {
737        return (mBitmapState.mTint != null && mBitmapState.mTint.isStateful())
738                || super.isStateful();
739    }
740
741    @Override
742    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
743            throws XmlPullParserException, IOException {
744        super.inflate(r, parser, attrs, theme);
745
746        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.BitmapDrawable);
747        updateStateFromTypedArray(a);
748        verifyRequiredAttributes(a);
749        a.recycle();
750
751        // Update local properties.
752        updateLocalState(r);
753    }
754
755    /**
756     * Ensures all required attributes are set.
757     *
758     * @throws XmlPullParserException if any required attributes are missing
759     */
760    private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
761        // If we're not waiting on a theme, verify required attributes.
762        final BitmapState state = mBitmapState;
763        if (state.mBitmap == null && (state.mThemeAttrs == null
764                || state.mThemeAttrs[R.styleable.BitmapDrawable_src] == 0)) {
765            throw new XmlPullParserException(a.getPositionDescription() +
766                    ": <bitmap> requires a valid 'src' attribute");
767        }
768    }
769
770    /**
771     * Updates the constant state from the values in the typed array.
772     */
773    private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
774        final Resources r = a.getResources();
775        final BitmapState state = mBitmapState;
776
777        // Account for any configuration changes.
778        state.mChangingConfigurations |= a.getChangingConfigurations();
779
780        // Extract the theme attributes, if any.
781        state.mThemeAttrs = a.extractThemeAttrs();
782
783        final int srcResId = a.getResourceId(R.styleable.BitmapDrawable_src, 0);
784        if (srcResId != 0) {
785            final Bitmap bitmap = BitmapFactory.decodeResource(r, srcResId);
786            if (bitmap == null) {
787                throw new XmlPullParserException(a.getPositionDescription() +
788                        ": <bitmap> requires a valid 'src' attribute");
789            }
790
791            state.mBitmap = bitmap;
792        }
793
794        state.mTargetDensity = r.getDisplayMetrics().densityDpi;
795
796        final boolean defMipMap = state.mBitmap != null ? state.mBitmap.hasMipMap() : false;
797        setMipMap(a.getBoolean(R.styleable.BitmapDrawable_mipMap, defMipMap));
798
799        state.mAutoMirrored = a.getBoolean(
800                R.styleable.BitmapDrawable_autoMirrored, state.mAutoMirrored);
801        state.mBaseAlpha = a.getFloat(R.styleable.BitmapDrawable_alpha, state.mBaseAlpha);
802
803        final int tintMode = a.getInt(R.styleable.BitmapDrawable_tintMode, -1);
804        if (tintMode != -1) {
805            state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
806        }
807
808        final ColorStateList tint = a.getColorStateList(R.styleable.BitmapDrawable_tint);
809        if (tint != null) {
810            state.mTint = tint;
811        }
812
813        final Paint paint = mBitmapState.mPaint;
814        paint.setAntiAlias(a.getBoolean(
815                R.styleable.BitmapDrawable_antialias, paint.isAntiAlias()));
816        paint.setFilterBitmap(a.getBoolean(
817                R.styleable.BitmapDrawable_filter, paint.isFilterBitmap()));
818        paint.setDither(a.getBoolean(R.styleable.BitmapDrawable_dither, paint.isDither()));
819
820        setGravity(a.getInt(R.styleable.BitmapDrawable_gravity, state.mGravity));
821
822        final int tileMode = a.getInt(R.styleable.BitmapDrawable_tileMode, TILE_MODE_UNDEFINED);
823        if (tileMode != TILE_MODE_UNDEFINED) {
824            final Shader.TileMode mode = parseTileMode(tileMode);
825            setTileModeXY(mode, mode);
826        }
827
828        final int tileModeX = a.getInt(R.styleable.BitmapDrawable_tileModeX, TILE_MODE_UNDEFINED);
829        if (tileModeX != TILE_MODE_UNDEFINED) {
830            setTileModeX(parseTileMode(tileModeX));
831        }
832
833        final int tileModeY = a.getInt(R.styleable.BitmapDrawable_tileModeY, TILE_MODE_UNDEFINED);
834        if (tileModeY != TILE_MODE_UNDEFINED) {
835            setTileModeY(parseTileMode(tileModeY));
836        }
837
838        state.mTargetDensity = Drawable.resolveDensity(r, 0);
839    }
840
841    @Override
842    public void applyTheme(Theme t) {
843        super.applyTheme(t);
844
845        final BitmapState state = mBitmapState;
846        if (state == null) {
847            return;
848        }
849
850        if (state.mThemeAttrs != null) {
851            final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.BitmapDrawable);
852            try {
853                updateStateFromTypedArray(a);
854            } catch (XmlPullParserException e) {
855                rethrowAsRuntimeException(e);
856            } finally {
857                a.recycle();
858            }
859        }
860
861        // Apply theme to contained color state list.
862        if (state.mTint != null && state.mTint.canApplyTheme()) {
863            state.mTint = state.mTint.obtainForTheme(t);
864        }
865
866        // Update local properties.
867        updateLocalState(t.getResources());
868    }
869
870    private static Shader.TileMode parseTileMode(int tileMode) {
871        switch (tileMode) {
872            case TILE_MODE_CLAMP:
873                return Shader.TileMode.CLAMP;
874            case TILE_MODE_REPEAT:
875                return Shader.TileMode.REPEAT;
876            case TILE_MODE_MIRROR:
877                return Shader.TileMode.MIRROR;
878            default:
879                return null;
880        }
881    }
882
883    @Override
884    public boolean canApplyTheme() {
885        return mBitmapState != null && mBitmapState.canApplyTheme();
886    }
887
888    @Override
889    public int getIntrinsicWidth() {
890        return mBitmapWidth;
891    }
892
893    @Override
894    public int getIntrinsicHeight() {
895        return mBitmapHeight;
896    }
897
898    @Override
899    public int getOpacity() {
900        if (mBitmapState.mGravity != Gravity.FILL) {
901            return PixelFormat.TRANSLUCENT;
902        }
903
904        final Bitmap bitmap = mBitmapState.mBitmap;
905        return (bitmap == null || bitmap.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ?
906                PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
907    }
908
909    @Override
910    public final ConstantState getConstantState() {
911        mBitmapState.mChangingConfigurations |= getChangingConfigurations();
912        return mBitmapState;
913    }
914
915    final static class BitmapState extends ConstantState {
916        final Paint mPaint;
917
918        // Values loaded during inflation.
919        int[] mThemeAttrs = null;
920        Bitmap mBitmap = null;
921        ColorStateList mTint = null;
922        Mode mTintMode = DEFAULT_TINT_MODE;
923        int mGravity = Gravity.FILL;
924        float mBaseAlpha = 1.0f;
925        Shader.TileMode mTileModeX = null;
926        Shader.TileMode mTileModeY = null;
927        int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
928        boolean mAutoMirrored = false;
929
930        @Config int mChangingConfigurations;
931        boolean mRebuildShader;
932
933        BitmapState(Bitmap bitmap) {
934            mBitmap = bitmap;
935            mPaint = new Paint(DEFAULT_PAINT_FLAGS);
936        }
937
938        BitmapState(BitmapState bitmapState) {
939            mBitmap = bitmapState.mBitmap;
940            mTint = bitmapState.mTint;
941            mTintMode = bitmapState.mTintMode;
942            mThemeAttrs = bitmapState.mThemeAttrs;
943            mChangingConfigurations = bitmapState.mChangingConfigurations;
944            mGravity = bitmapState.mGravity;
945            mTileModeX = bitmapState.mTileModeX;
946            mTileModeY = bitmapState.mTileModeY;
947            mTargetDensity = bitmapState.mTargetDensity;
948            mBaseAlpha = bitmapState.mBaseAlpha;
949            mPaint = new Paint(bitmapState.mPaint);
950            mRebuildShader = bitmapState.mRebuildShader;
951            mAutoMirrored = bitmapState.mAutoMirrored;
952        }
953
954        @Override
955        public boolean canApplyTheme() {
956            return mThemeAttrs != null || mTint != null && mTint.canApplyTheme();
957        }
958
959        @Override
960        public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
961            if (isAtlasable(mBitmap) && atlasList.add(mBitmap)) {
962                return mBitmap.getWidth() * mBitmap.getHeight();
963            }
964            return 0;
965        }
966
967        @Override
968        public Drawable newDrawable() {
969            return new BitmapDrawable(this, null);
970        }
971
972        @Override
973        public Drawable newDrawable(Resources res) {
974            return new BitmapDrawable(this, res);
975        }
976
977        @Override
978        public @Config int getChangingConfigurations() {
979            return mChangingConfigurations
980                    | (mTint != null ? mTint.getChangingConfigurations() : 0);
981        }
982    }
983
984    /**
985     * The one constructor to rule them all. This is called by all public
986     * constructors to set the state and initialize local properties.
987     */
988    private BitmapDrawable(BitmapState state, Resources res) {
989        mBitmapState = state;
990
991        updateLocalState(res);
992    }
993
994    /**
995     * Initializes local dynamic properties from state. This should be called
996     * after significant state changes, e.g. from the One True Constructor and
997     * after inflating or applying a theme.
998     */
999    private void updateLocalState(Resources res) {
1000        mTargetDensity = resolveDensity(res, mBitmapState.mTargetDensity);
1001        mTintFilter = updateTintFilter(mTintFilter, mBitmapState.mTint, mBitmapState.mTintMode);
1002        computeBitmapSize();
1003    }
1004}
1005