1  /*
2   * Copyright (C) 2020 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 com.android.systemui.util;
18  
19  import android.content.res.ColorStateList;
20  import android.content.res.Resources;
21  import android.content.res.Resources.Theme;
22  import android.content.res.TypedArray;
23  import android.graphics.drawable.Drawable;
24  import android.graphics.drawable.DrawableWrapper;
25  import android.graphics.drawable.InsetDrawable;
26  import android.util.AttributeSet;
27  
28  import androidx.annotation.NonNull;
29  import androidx.annotation.Nullable;
30  
31  import com.android.systemui.R;
32  
33  import org.xmlpull.v1.XmlPullParser;
34  import org.xmlpull.v1.XmlPullParserException;
35  
36  import java.io.IOException;
37  
38  /**
39   * An extension of {@link DrawableWrapper} that supports alpha and tint XML properties.
40   *
41   * {@link DrawableWrapper} supports setting these properties programmatically, but doesn't expose
42   * corresponding XML properties for some reason. This class allows to set these values in the XML,
43   * supporting theming.
44   *
45   * This class should only be used in XML.
46   *
47   * @attr ref android.R.styleable#DrawableWrapper_drawable
48   * @attr ref R.styleable#AlphaTintDrawableWrapper_tint
49   * @attr ref R.styleable#AlphaTintDrawableWrapper_alpha
50   */
51  public class AlphaTintDrawableWrapper extends InsetDrawable {
52      private ColorStateList mTint;
53      private int[] mThemeAttrs;
54  
55      /** No-arg constructor used by drawable inflation. */
AlphaTintDrawableWrapper()56      public AlphaTintDrawableWrapper() {
57          super(null, 0);
58      }
59  
AlphaTintDrawableWrapper(Drawable drawable, int[] themeAttrs)60      AlphaTintDrawableWrapper(Drawable drawable, int[] themeAttrs) {
61          super(drawable, 0);
62          mThemeAttrs = themeAttrs;
63      }
64  
65      @Override
inflate(@onNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)66      public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
67              @NonNull AttributeSet attrs, @Nullable Theme theme)
68              throws XmlPullParserException, IOException {
69          final TypedArray a = obtainAttributes(r, theme, attrs,
70                  R.styleable.AlphaTintDrawableWrapper);
71  
72          super.inflate(r, parser, attrs, theme);
73  
74          mThemeAttrs = a.extractThemeAttrs();
75          updateStateFromTypedArray(a);
76          a.recycle();
77  
78          applyTint();
79      }
80  
81      @Override
applyTheme(Theme t)82      public void applyTheme(Theme t) {
83          super.applyTheme(t);
84  
85          if (mThemeAttrs != null && t != null) {
86              final TypedArray a = t.resolveAttributes(mThemeAttrs,
87                      R.styleable.AlphaTintDrawableWrapper);
88              updateStateFromTypedArray(a);
89              a.recycle();
90          }
91  
92          // Ensure tint is reapplied after applying the theme to ensure this drawables'
93          // tint overrides the underlying drawables' tint.
94          applyTint();
95      }
96  
97      @Override
canApplyTheme()98      public boolean canApplyTheme() {
99          return (mThemeAttrs != null && mThemeAttrs.length > 0) || super.canApplyTheme();
100      }
101  
updateStateFromTypedArray(@onNull TypedArray a)102      private void updateStateFromTypedArray(@NonNull TypedArray a) {
103          if (a.hasValue(R.styleable.AlphaTintDrawableWrapper_android_tint)) {
104              mTint = a.getColorStateList(R.styleable.AlphaTintDrawableWrapper_android_tint);
105          }
106          if (a.hasValue(R.styleable.AlphaTintDrawableWrapper_android_alpha)) {
107              float alpha = a.getFloat(R.styleable.AlphaTintDrawableWrapper_android_alpha, 1);
108              setAlpha(Math.round(alpha * 255));
109          }
110      }
111  
112      @Override
setTintList(ColorStateList tint)113      public void setTintList(ColorStateList tint) {
114          super.setTintList(tint);
115          mTint = tint;
116      }
117  
applyTint()118      private void applyTint() {
119          if (getDrawable() != null && mTint != null) {
120              getDrawable().mutate().setTintList(mTint);
121          }
122      }
123  
124      @Nullable
125      @Override
getConstantState()126      public ConstantState getConstantState() {
127          return new AlphaTintState(super.getConstantState(), mThemeAttrs, getAlpha(), mTint);
128      }
129  
130      static class AlphaTintState extends Drawable.ConstantState {
131  
132          private ConstantState mWrappedState;
133          private int[] mThemeAttrs;
134          private int mAlpha;
135          private ColorStateList mColorStateList;
136  
AlphaTintState( ConstantState wrappedState, int[] themeAttrs, int alpha, ColorStateList colorStateList )137          AlphaTintState(
138                  ConstantState wrappedState,
139                  int[] themeAttrs,
140                  int alpha,
141                  ColorStateList colorStateList
142          ) {
143              mWrappedState = wrappedState;
144              mThemeAttrs = themeAttrs;
145              mAlpha = alpha;
146              mColorStateList = colorStateList;
147          }
148  
149          @NonNull
150          @Override
newDrawable()151          public Drawable newDrawable() {
152              return newDrawable(null, null);
153          }
154  
155          @NonNull
156          @Override
newDrawable(Resources res, Theme theme)157          public Drawable newDrawable(Resources res, Theme theme) {
158              DrawableWrapper wrapper = (DrawableWrapper) mWrappedState.newDrawable(res, theme);
159              AlphaTintDrawableWrapper alphaTintDrawableWrapper =
160                      new AlphaTintDrawableWrapper(wrapper.getDrawable(), mThemeAttrs);
161              alphaTintDrawableWrapper.setTintList(mColorStateList);
162              alphaTintDrawableWrapper.setAlpha(mAlpha);
163              return alphaTintDrawableWrapper;
164          }
165  
166          @Override
canApplyTheme()167          public boolean canApplyTheme() {
168              return true;
169          }
170  
171          @Override
getChangingConfigurations()172          public int getChangingConfigurations() {
173              return mWrappedState.getChangingConfigurations();
174          }
175      }
176  }
177