1/*
2 * Copyright (C) 2008 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.ColorInt;
20import android.annotation.NonNull;
21import android.content.pm.ActivityInfo.Config;
22import android.graphics.*;
23import android.graphics.PorterDuff.Mode;
24import android.content.res.ColorStateList;
25import android.content.res.Resources;
26import android.content.res.Resources.Theme;
27import android.content.res.TypedArray;
28import android.util.AttributeSet;
29import android.view.ViewDebug;
30
31import com.android.internal.R;
32
33import org.xmlpull.v1.XmlPullParser;
34import org.xmlpull.v1.XmlPullParserException;
35
36import java.io.IOException;
37
38/**
39 * A specialized Drawable that fills the Canvas with a specified color.
40 * Note that a ColorDrawable ignores the ColorFilter.
41 *
42 * <p>It can be defined in an XML file with the <code>&lt;color></code> element.</p>
43 *
44 * @attr ref android.R.styleable#ColorDrawable_color
45 */
46public class ColorDrawable extends Drawable {
47    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
48
49    @ViewDebug.ExportedProperty(deepExport = true, prefix = "state_")
50    private ColorState mColorState;
51    private PorterDuffColorFilter mTintFilter;
52
53    private boolean mMutated;
54
55    /**
56     * Creates a new black ColorDrawable.
57     */
58    public ColorDrawable() {
59        mColorState = new ColorState();
60    }
61
62    /**
63     * Creates a new ColorDrawable with the specified color.
64     *
65     * @param color The color to draw.
66     */
67    public ColorDrawable(@ColorInt int color) {
68        mColorState = new ColorState();
69
70        setColor(color);
71    }
72
73    @Override
74    public @Config int getChangingConfigurations() {
75        return super.getChangingConfigurations() | mColorState.getChangingConfigurations();
76    }
77
78    /**
79     * A mutable BitmapDrawable still shares its Bitmap with any other Drawable
80     * that comes from the same resource.
81     *
82     * @return This drawable.
83     */
84    @Override
85    public Drawable mutate() {
86        if (!mMutated && super.mutate() == this) {
87            mColorState = new ColorState(mColorState);
88            mMutated = true;
89        }
90        return this;
91    }
92
93    /**
94     * @hide
95     */
96    public void clearMutated() {
97        super.clearMutated();
98        mMutated = false;
99    }
100
101    @Override
102    public void draw(Canvas canvas) {
103        final ColorFilter colorFilter = mPaint.getColorFilter();
104        if ((mColorState.mUseColor >>> 24) != 0 || colorFilter != null || mTintFilter != null) {
105            if (colorFilter == null) {
106                mPaint.setColorFilter(mTintFilter);
107            }
108
109            mPaint.setColor(mColorState.mUseColor);
110            canvas.drawRect(getBounds(), mPaint);
111
112            // Restore original color filter.
113            mPaint.setColorFilter(colorFilter);
114        }
115    }
116
117    /**
118     * Gets the drawable's color value.
119     *
120     * @return int The color to draw.
121     */
122    @ColorInt
123    public int getColor() {
124        return mColorState.mUseColor;
125    }
126
127    /**
128     * Sets the drawable's color value. This action will clobber the results of
129     * prior calls to {@link #setAlpha(int)} on this object, which side-affected
130     * the underlying color.
131     *
132     * @param color The color to draw.
133     */
134    public void setColor(@ColorInt int color) {
135        if (mColorState.mBaseColor != color || mColorState.mUseColor != color) {
136            mColorState.mBaseColor = mColorState.mUseColor = color;
137            invalidateSelf();
138        }
139    }
140
141    /**
142     * Returns the alpha value of this drawable's color.
143     *
144     * @return A value between 0 and 255.
145     */
146    @Override
147    public int getAlpha() {
148        return mColorState.mUseColor >>> 24;
149    }
150
151    /**
152     * Sets the color's alpha value.
153     *
154     * @param alpha The alpha value to set, between 0 and 255.
155     */
156    @Override
157    public void setAlpha(int alpha) {
158        alpha += alpha >> 7;   // make it 0..256
159        final int baseAlpha = mColorState.mBaseColor >>> 24;
160        final int useAlpha = baseAlpha * alpha >> 8;
161        final int useColor = (mColorState.mBaseColor << 8 >>> 8) | (useAlpha << 24);
162        if (mColorState.mUseColor != useColor) {
163            mColorState.mUseColor = useColor;
164            invalidateSelf();
165        }
166    }
167
168    /**
169     * Sets the color filter applied to this color.
170     * <p>
171     * Only supported on version {@link android.os.Build.VERSION_CODES#LOLLIPOP} and
172     * above. Calling this method has no effect on earlier versions.
173     *
174     * @see android.graphics.drawable.Drawable#setColorFilter(ColorFilter)
175     */
176    @Override
177    public void setColorFilter(ColorFilter colorFilter) {
178        mPaint.setColorFilter(colorFilter);
179    }
180
181    @Override
182    public void setTintList(ColorStateList tint) {
183        mColorState.mTint = tint;
184        mTintFilter = updateTintFilter(mTintFilter, tint, mColorState.mTintMode);
185        invalidateSelf();
186    }
187
188    @Override
189    public void setTintMode(Mode tintMode) {
190        mColorState.mTintMode = tintMode;
191        mTintFilter = updateTintFilter(mTintFilter, mColorState.mTint, tintMode);
192        invalidateSelf();
193    }
194
195    @Override
196    protected boolean onStateChange(int[] stateSet) {
197        final ColorState state = mColorState;
198        if (state.mTint != null && state.mTintMode != null) {
199            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
200            return true;
201        }
202        return false;
203    }
204
205    @Override
206    public boolean isStateful() {
207        return mColorState.mTint != null && mColorState.mTint.isStateful();
208    }
209
210    @Override
211    public int getOpacity() {
212        if (mTintFilter != null || mPaint.getColorFilter() != null) {
213            return PixelFormat.TRANSLUCENT;
214        }
215
216        switch (mColorState.mUseColor >>> 24) {
217            case 255:
218                return PixelFormat.OPAQUE;
219            case 0:
220                return PixelFormat.TRANSPARENT;
221        }
222        return PixelFormat.TRANSLUCENT;
223    }
224
225    @Override
226    public void getOutline(@NonNull Outline outline) {
227        outline.setRect(getBounds());
228        outline.setAlpha(getAlpha() / 255.0f);
229    }
230
231    @Override
232    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
233            throws XmlPullParserException, IOException {
234        super.inflate(r, parser, attrs, theme);
235
236        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ColorDrawable);
237        updateStateFromTypedArray(a);
238        a.recycle();
239
240        updateLocalState(r);
241    }
242
243    /**
244     * Updates the constant state from the values in the typed array.
245     */
246    private void updateStateFromTypedArray(TypedArray a) {
247        final ColorState state = mColorState;
248
249        // Account for any configuration changes.
250        state.mChangingConfigurations |= a.getChangingConfigurations();
251
252        // Extract the theme attributes, if any.
253        state.mThemeAttrs = a.extractThemeAttrs();
254
255        state.mBaseColor = a.getColor(R.styleable.ColorDrawable_color, state.mBaseColor);
256        state.mUseColor = state.mBaseColor;
257    }
258
259    @Override
260    public boolean canApplyTheme() {
261        return mColorState.canApplyTheme() || super.canApplyTheme();
262    }
263
264    @Override
265    public void applyTheme(Theme t) {
266        super.applyTheme(t);
267
268        final ColorState state = mColorState;
269        if (state == null) {
270            return;
271        }
272
273        if (state.mThemeAttrs != null) {
274            final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ColorDrawable);
275            updateStateFromTypedArray(a);
276            a.recycle();
277        }
278
279        if (state.mTint != null && state.mTint.canApplyTheme()) {
280            state.mTint = state.mTint.obtainForTheme(t);
281        }
282
283        updateLocalState(t.getResources());
284    }
285
286    @Override
287    public ConstantState getConstantState() {
288        return mColorState;
289    }
290
291    final static class ColorState extends ConstantState {
292        int[] mThemeAttrs;
293        int mBaseColor; // base color, independent of setAlpha()
294        @ViewDebug.ExportedProperty
295        int mUseColor;  // basecolor modulated by setAlpha()
296        @Config int mChangingConfigurations;
297        ColorStateList mTint = null;
298        Mode mTintMode = DEFAULT_TINT_MODE;
299
300        ColorState() {
301            // Empty constructor.
302        }
303
304        ColorState(ColorState state) {
305            mThemeAttrs = state.mThemeAttrs;
306            mBaseColor = state.mBaseColor;
307            mUseColor = state.mUseColor;
308            mChangingConfigurations = state.mChangingConfigurations;
309            mTint = state.mTint;
310            mTintMode = state.mTintMode;
311        }
312
313        @Override
314        public boolean canApplyTheme() {
315            return mThemeAttrs != null
316                    || (mTint != null && mTint.canApplyTheme());
317        }
318
319        @Override
320        public Drawable newDrawable() {
321            return new ColorDrawable(this, null);
322        }
323
324        @Override
325        public Drawable newDrawable(Resources res) {
326            return new ColorDrawable(this, res);
327        }
328
329        @Override
330        public @Config int getChangingConfigurations() {
331            return mChangingConfigurations
332                    | (mTint != null ? mTint.getChangingConfigurations() : 0);
333        }
334    }
335
336    private ColorDrawable(ColorState state, Resources res) {
337        mColorState = state;
338
339        updateLocalState(res);
340    }
341
342    /**
343     * Initializes local dynamic properties from state. This should be called
344     * after significant state changes, e.g. from the One True Constructor and
345     * after inflating or applying a theme.
346     */
347    private void updateLocalState(Resources r) {
348        mTintFilter = updateTintFilter(mTintFilter, mColorState.mTint, mColorState.mTintMode);
349    }
350}
351