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