1 /*
2  * Copyright (C) 2015 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 androidx.appcompat.widget;
18 
19 import android.content.res.ColorStateList;
20 import android.graphics.PorterDuff;
21 import android.graphics.drawable.Drawable;
22 import android.os.Build;
23 import android.util.AttributeSet;
24 import android.view.View;
25 
26 import androidx.annotation.NonNull;
27 import androidx.appcompat.R;
28 import androidx.core.view.ViewCompat;
29 
30 class AppCompatBackgroundHelper {
31 
32     private final View mView;
33     private final AppCompatDrawableManager mDrawableManager;
34 
35     private int mBackgroundResId = -1;
36 
37     private TintInfo mInternalBackgroundTint;
38     private TintInfo mBackgroundTint;
39     private TintInfo mTmpInfo;
40 
AppCompatBackgroundHelper(View view)41     AppCompatBackgroundHelper(View view) {
42         mView = view;
43         mDrawableManager = AppCompatDrawableManager.get();
44     }
45 
loadFromAttributes(AttributeSet attrs, int defStyleAttr)46     void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
47         TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs,
48                 R.styleable.ViewBackgroundHelper, defStyleAttr, 0);
49         try {
50             if (a.hasValue(R.styleable.ViewBackgroundHelper_android_background)) {
51                 mBackgroundResId = a.getResourceId(
52                         R.styleable.ViewBackgroundHelper_android_background, -1);
53                 ColorStateList tint = mDrawableManager
54                         .getTintList(mView.getContext(), mBackgroundResId);
55                 if (tint != null) {
56                     setInternalBackgroundTint(tint);
57                 }
58             }
59             if (a.hasValue(R.styleable.ViewBackgroundHelper_backgroundTint)) {
60                 ViewCompat.setBackgroundTintList(mView,
61                         a.getColorStateList(R.styleable.ViewBackgroundHelper_backgroundTint));
62             }
63             if (a.hasValue(R.styleable.ViewBackgroundHelper_backgroundTintMode)) {
64                 ViewCompat.setBackgroundTintMode(mView,
65                         DrawableUtils.parseTintMode(
66                                 a.getInt(R.styleable.ViewBackgroundHelper_backgroundTintMode, -1),
67                                 null));
68             }
69         } finally {
70             a.recycle();
71         }
72     }
73 
onSetBackgroundResource(int resId)74     void onSetBackgroundResource(int resId) {
75         mBackgroundResId = resId;
76         // Update the default background tint
77         setInternalBackgroundTint(mDrawableManager != null
78                 ? mDrawableManager.getTintList(mView.getContext(), resId)
79                 : null);
80         applySupportBackgroundTint();
81     }
82 
onSetBackgroundDrawable(Drawable background)83     void onSetBackgroundDrawable(Drawable background) {
84         mBackgroundResId = -1;
85         // We don't know that this drawable is, so we need to clear the default background tint
86         setInternalBackgroundTint(null);
87         applySupportBackgroundTint();
88     }
89 
setSupportBackgroundTintList(ColorStateList tint)90     void setSupportBackgroundTintList(ColorStateList tint) {
91         if (mBackgroundTint == null) {
92             mBackgroundTint = new TintInfo();
93         }
94         mBackgroundTint.mTintList = tint;
95         mBackgroundTint.mHasTintList = true;
96         applySupportBackgroundTint();
97     }
98 
getSupportBackgroundTintList()99     ColorStateList getSupportBackgroundTintList() {
100         return mBackgroundTint != null ? mBackgroundTint.mTintList : null;
101     }
102 
setSupportBackgroundTintMode(PorterDuff.Mode tintMode)103     void setSupportBackgroundTintMode(PorterDuff.Mode tintMode) {
104         if (mBackgroundTint == null) {
105             mBackgroundTint = new TintInfo();
106         }
107         mBackgroundTint.mTintMode = tintMode;
108         mBackgroundTint.mHasTintMode = true;
109 
110         applySupportBackgroundTint();
111     }
112 
getSupportBackgroundTintMode()113     PorterDuff.Mode getSupportBackgroundTintMode() {
114         return mBackgroundTint != null ? mBackgroundTint.mTintMode : null;
115     }
116 
applySupportBackgroundTint()117     void applySupportBackgroundTint() {
118         final Drawable background = mView.getBackground();
119         if (background != null) {
120             if (shouldApplyFrameworkTintUsingColorFilter()
121                     && applyFrameworkTintUsingColorFilter(background)) {
122                 // This needs to be called before the internal tints below so it takes
123                 // effect on any widgets using the compat tint on API 21 (EditText)
124                 return;
125             }
126 
127             if (mBackgroundTint != null) {
128                 AppCompatDrawableManager.tintDrawable(background, mBackgroundTint,
129                         mView.getDrawableState());
130             } else if (mInternalBackgroundTint != null) {
131                 AppCompatDrawableManager.tintDrawable(background, mInternalBackgroundTint,
132                         mView.getDrawableState());
133             }
134         }
135     }
136 
setInternalBackgroundTint(ColorStateList tint)137     void setInternalBackgroundTint(ColorStateList tint) {
138         if (tint != null) {
139             if (mInternalBackgroundTint == null) {
140                 mInternalBackgroundTint = new TintInfo();
141             }
142             mInternalBackgroundTint.mTintList = tint;
143             mInternalBackgroundTint.mHasTintList = true;
144         } else {
145             mInternalBackgroundTint = null;
146         }
147         applySupportBackgroundTint();
148     }
149 
shouldApplyFrameworkTintUsingColorFilter()150     private boolean shouldApplyFrameworkTintUsingColorFilter() {
151         final int sdk = Build.VERSION.SDK_INT;
152         if (sdk > 21) {
153             // On API 22+, if we're using an internal compat background tint, we're also
154             // responsible for applying any custom tint set via the framework impl
155             return mInternalBackgroundTint != null;
156         } else if (sdk == 21) {
157             // GradientDrawable doesn't implement setTintList on API 21, and since there is
158             // no nice way to unwrap DrawableContainers we have to blanket apply this
159             // on API 21
160             return true;
161         } else {
162             // API 19 and below doesn't have framework tint
163             return false;
164         }
165     }
166 
167     /**
168      * Applies the framework background tint to a view, but using the compat method (ColorFilter)
169      *
170      * @return true if a tint was applied
171      */
applyFrameworkTintUsingColorFilter(@onNull Drawable background)172     private boolean applyFrameworkTintUsingColorFilter(@NonNull Drawable background) {
173         if (mTmpInfo == null) {
174             mTmpInfo = new TintInfo();
175         }
176         final TintInfo info = mTmpInfo;
177         info.clear();
178 
179         final ColorStateList tintList = ViewCompat.getBackgroundTintList(mView);
180         if (tintList != null) {
181             info.mHasTintList = true;
182             info.mTintList = tintList;
183         }
184         final PorterDuff.Mode mode = ViewCompat.getBackgroundTintMode(mView);
185         if (mode != null) {
186             info.mHasTintMode = true;
187             info.mTintMode = mode;
188         }
189 
190         if (info.mHasTintList || info.mHasTintMode) {
191             AppCompatDrawableManager.tintDrawable(background, info, mView.getDrawableState());
192             return true;
193         }
194 
195         return false;
196     }
197 }
198