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