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