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