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