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