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.os.Build;
38 import android.util.AttributeSet;
39 import android.view.ViewDebug;
40 
41 import com.android.internal.R;
42 
43 import org.xmlpull.v1.XmlPullParser;
44 import org.xmlpull.v1.XmlPullParserException;
45 
46 import java.io.IOException;
47 
48 /**
49  * A specialized Drawable that fills the Canvas with a specified color.
50  * Note that a ColorDrawable ignores the ColorFilter.
51  *
52  * <p>It can be defined in an XML file with the <code>&lt;color></code> element.</p>
53  *
54  * @attr ref android.R.styleable#ColorDrawable_color
55  */
56 public class ColorDrawable extends Drawable {
57     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
58     private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
59 
60     @ViewDebug.ExportedProperty(deepExport = true, prefix = "state_")
61     private ColorState mColorState;
62     private BlendModeColorFilter mBlendModeColorFilter;
63 
64     private boolean mMutated;
65 
66     /**
67      * Creates a new black ColorDrawable.
68      */
ColorDrawable()69     public ColorDrawable() {
70         mColorState = new ColorState();
71     }
72 
73     /**
74      * Creates a new ColorDrawable with the specified color.
75      *
76      * @param color The color to draw.
77      */
ColorDrawable(@olorInt int color)78     public ColorDrawable(@ColorInt int color) {
79         mColorState = new ColorState();
80 
81         setColor(color);
82     }
83 
84     @Override
getChangingConfigurations()85     public @Config int getChangingConfigurations() {
86         return super.getChangingConfigurations() | mColorState.getChangingConfigurations();
87     }
88 
89     /**
90      * A mutable BitmapDrawable still shares its Bitmap with any other Drawable
91      * that comes from the same resource.
92      *
93      * @return This drawable.
94      */
95     @Override
mutate()96     public Drawable mutate() {
97         if (!mMutated && super.mutate() == this) {
98             mColorState = new ColorState(mColorState);
99             mMutated = true;
100         }
101         return this;
102     }
103 
104     /**
105      * @hide
106      */
clearMutated()107     public void clearMutated() {
108         super.clearMutated();
109         mMutated = false;
110     }
111 
112     @Override
draw(Canvas canvas)113     public void draw(Canvas canvas) {
114         final ColorFilter colorFilter = mPaint.getColorFilter();
115         if ((mColorState.mUseColor >>> 24) != 0 || colorFilter != null
116                 || mBlendModeColorFilter != null) {
117             if (colorFilter == null) {
118                 mPaint.setColorFilter(mBlendModeColorFilter);
119             }
120 
121             mPaint.setColor(mColorState.mUseColor);
122             canvas.drawRect(getBounds(), mPaint);
123 
124             // Restore original color filter.
125             mPaint.setColorFilter(colorFilter);
126         }
127     }
128 
129     /**
130      * Gets the drawable's color value.
131      *
132      * @return int The color to draw.
133      */
134     @ColorInt
getColor()135     public int getColor() {
136         return mColorState.mUseColor;
137     }
138 
139     /**
140      * Sets the drawable's color value. This action will clobber the results of
141      * prior calls to {@link #setAlpha(int)} on this object, which side-affected
142      * the underlying color.
143      *
144      * @param color The color to draw.
145      */
setColor(@olorInt int color)146     public void setColor(@ColorInt int color) {
147         if (mColorState.mBaseColor != color || mColorState.mUseColor != color) {
148             mColorState.mBaseColor = mColorState.mUseColor = color;
149             invalidateSelf();
150         }
151     }
152 
153     /**
154      * Returns the alpha value of this drawable's color. Note this may not be the same alpha value
155      * provided in {@link Drawable#setAlpha(int)}. Instead this will return the alpha of the color
156      * combined with the alpha provided by setAlpha
157      *
158      * @return A value between 0 and 255.
159      *
160      * @see ColorDrawable#setAlpha(int)
161      */
162     @Override
getAlpha()163     public int getAlpha() {
164         return mColorState.mUseColor >>> 24;
165     }
166 
167     /**
168      * Applies the given alpha to the underlying color. Note if the color already has
169      * an alpha applied to it, this will apply this alpha to the existing value instead of
170      * overwriting it.
171      *
172      * @param alpha The alpha value to set, between 0 and 255.
173      */
174     @Override
setAlpha(int alpha)175     public void setAlpha(int alpha) {
176         alpha += alpha >> 7;   // make it 0..256
177         final int baseAlpha = mColorState.mBaseColor >>> 24;
178         final int useAlpha = baseAlpha * alpha >> 8;
179         final int useColor = (mColorState.mBaseColor << 8 >>> 8) | (useAlpha << 24);
180         if (mColorState.mUseColor != useColor) {
181             mColorState.mUseColor = useColor;
182             invalidateSelf();
183         }
184     }
185 
186     /**
187      * Sets the color filter applied to this color.
188      * <p>
189      * Only supported on version {@link android.os.Build.VERSION_CODES#LOLLIPOP} and
190      * above. Calling this method has no effect on earlier versions.
191      *
192      * @see android.graphics.drawable.Drawable#setColorFilter(ColorFilter)
193      */
194     @Override
setColorFilter(ColorFilter colorFilter)195     public void setColorFilter(ColorFilter colorFilter) {
196         mPaint.setColorFilter(colorFilter);
197     }
198 
199     /**
200      * Returns the color filter applied to this color configured by
201      * {@link #setColorFilter(ColorFilter)}
202      *
203      * @see android.graphics.drawable.Drawable#getColorFilter()
204      */
205     @Override
getColorFilter()206     public @Nullable ColorFilter getColorFilter() {
207         return mPaint.getColorFilter();
208     }
209 
210     @Override
setTintList(ColorStateList tint)211     public void setTintList(ColorStateList tint) {
212         mColorState.mTint = tint;
213         mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, tint,
214                 mColorState.mBlendMode);
215         invalidateSelf();
216     }
217 
218     @Override
setTintBlendMode(@onNull BlendMode blendMode)219     public void setTintBlendMode(@NonNull BlendMode blendMode) {
220         mColorState.mBlendMode = blendMode;
221         mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, mColorState.mTint,
222                 blendMode);
223         invalidateSelf();
224     }
225 
226     @Override
onStateChange(int[] stateSet)227     protected boolean onStateChange(int[] stateSet) {
228         final ColorState state = mColorState;
229         if (state.mTint != null && state.mBlendMode != null) {
230             mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, state.mTint,
231                     state.mBlendMode);
232             return true;
233         }
234         return false;
235     }
236 
237     @Override
isStateful()238     public boolean isStateful() {
239         return mColorState.mTint != null && mColorState.mTint.isStateful();
240     }
241 
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