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 static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20
21import android.content.res.ColorStateList;
22import android.graphics.PorterDuff;
23import android.graphics.drawable.Drawable;
24import android.os.Build;
25import android.support.annotation.NonNull;
26import android.support.annotation.RestrictTo;
27import android.support.v4.widget.ImageViewCompat;
28import android.support.v7.appcompat.R;
29import android.support.v7.content.res.AppCompatResources;
30import android.util.AttributeSet;
31import android.widget.ImageView;
32
33/**
34 * @hide
35 */
36@RestrictTo(LIBRARY_GROUP)
37public class AppCompatImageHelper {
38    private final ImageView mView;
39
40    private TintInfo mInternalImageTint;
41    private TintInfo mImageTint;
42    private TintInfo mTmpInfo;
43
44    public AppCompatImageHelper(ImageView view) {
45        mView = view;
46    }
47
48    public void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
49        TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs,
50                R.styleable.AppCompatImageView, defStyleAttr, 0);
51        try {
52            Drawable drawable = mView.getDrawable();
53            if (drawable == null) {
54                // If the view doesn't already have a drawable (from android:src), try loading
55                // it from srcCompat
56                final int id = a.getResourceId(R.styleable.AppCompatImageView_srcCompat, -1);
57                if (id != -1) {
58                    drawable = AppCompatResources.getDrawable(mView.getContext(), id);
59                    if (drawable != null) {
60                        mView.setImageDrawable(drawable);
61                    }
62                }
63            }
64
65            if (drawable != null) {
66                DrawableUtils.fixDrawable(drawable);
67            }
68
69            if (a.hasValue(R.styleable.AppCompatImageView_tint)) {
70                ImageViewCompat.setImageTintList(mView,
71                        a.getColorStateList(R.styleable.AppCompatImageView_tint));
72            }
73            if (a.hasValue(R.styleable.AppCompatImageView_tintMode)) {
74                ImageViewCompat.setImageTintMode(mView,
75                        DrawableUtils.parseTintMode(
76                                a.getInt(R.styleable.AppCompatImageView_tintMode, -1), null));
77            }
78        } finally {
79            a.recycle();
80        }
81    }
82
83    public void setImageResource(int resId) {
84        if (resId != 0) {
85            final Drawable d = AppCompatResources.getDrawable(mView.getContext(), resId);
86            if (d != null) {
87                DrawableUtils.fixDrawable(d);
88            }
89            mView.setImageDrawable(d);
90        } else {
91            mView.setImageDrawable(null);
92        }
93
94        applySupportImageTint();
95    }
96
97    boolean hasOverlappingRendering() {
98        final Drawable background = mView.getBackground();
99        if (Build.VERSION.SDK_INT >= 21
100                && background instanceof android.graphics.drawable.RippleDrawable) {
101            // RippleDrawable has an issue on L+ when used with an alpha animation.
102            // This workaround should be disabled when the platform bug is fixed. See b/27715789
103            return false;
104        }
105        return true;
106    }
107
108    void setSupportImageTintList(ColorStateList tint) {
109        if (mImageTint == null) {
110            mImageTint = new TintInfo();
111        }
112        mImageTint.mTintList = tint;
113        mImageTint.mHasTintList = true;
114        applySupportImageTint();
115    }
116
117    ColorStateList getSupportImageTintList() {
118        return mImageTint != null ? mImageTint.mTintList : null;
119    }
120
121    void setSupportImageTintMode(PorterDuff.Mode tintMode) {
122        if (mImageTint == null) {
123            mImageTint = new TintInfo();
124        }
125        mImageTint.mTintMode = tintMode;
126        mImageTint.mHasTintMode = true;
127
128        applySupportImageTint();
129    }
130
131    PorterDuff.Mode getSupportImageTintMode() {
132        return mImageTint != null ? mImageTint.mTintMode : null;
133    }
134
135    void applySupportImageTint() {
136        final Drawable imageViewDrawable = mView.getDrawable();
137        if (imageViewDrawable != null) {
138            DrawableUtils.fixDrawable(imageViewDrawable);
139        }
140
141        if (imageViewDrawable != null) {
142            if (shouldApplyFrameworkTintUsingColorFilter()
143                    && applyFrameworkTintUsingColorFilter(imageViewDrawable)) {
144                // This needs to be called before the internal tints below so it takes
145                // effect on any widgets using the compat tint on API 21
146                return;
147            }
148
149            if (mImageTint != null) {
150                AppCompatDrawableManager.tintDrawable(imageViewDrawable, mImageTint,
151                        mView.getDrawableState());
152            } else if (mInternalImageTint != null) {
153                AppCompatDrawableManager.tintDrawable(imageViewDrawable, mInternalImageTint,
154                        mView.getDrawableState());
155            }
156        }
157    }
158
159    void setInternalImageTint(ColorStateList tint) {
160        if (tint != null) {
161            if (mInternalImageTint == null) {
162                mInternalImageTint = new TintInfo();
163            }
164            mInternalImageTint.mTintList = tint;
165            mInternalImageTint.mHasTintList = true;
166        } else {
167            mInternalImageTint = null;
168        }
169        applySupportImageTint();
170    }
171
172    private boolean shouldApplyFrameworkTintUsingColorFilter() {
173        final int sdk = Build.VERSION.SDK_INT;
174        if (sdk > 21) {
175            // On API 22+, if we're using an internal compat image source tint, we're also
176            // responsible for applying any custom tint set via the framework impl
177            return mInternalImageTint != null;
178        } else if (sdk == 21) {
179            // GradientDrawable doesn't implement setTintList on API 21, and since there is
180            // no nice way to unwrap DrawableContainers we have to blanket apply this
181            // on API 21
182            return true;
183        } else {
184            // API 19 and below doesn't have framework tint
185            return false;
186        }
187    }
188
189    /**
190     * Applies the framework image source tint to a view, but using the compat method (ColorFilter)
191     *
192     * @return true if a tint was applied
193     */
194    private boolean applyFrameworkTintUsingColorFilter(@NonNull Drawable imageSource) {
195        if (mTmpInfo == null) {
196            mTmpInfo = new TintInfo();
197        }
198        final TintInfo info = mTmpInfo;
199        info.clear();
200
201        final ColorStateList tintList = ImageViewCompat.getImageTintList(mView);
202        if (tintList != null) {
203            info.mHasTintList = true;
204            info.mTintList = tintList;
205        }
206        final PorterDuff.Mode mode = ImageViewCompat.getImageTintMode(mView);
207        if (mode != null) {
208            info.mHasTintMode = true;
209            info.mTintMode = mode;
210        }
211
212        if (info.mHasTintList || info.mHasTintMode) {
213            AppCompatDrawableManager.tintDrawable(imageSource, info, mView.getDrawableState());
214            return true;
215        }
216
217        return false;
218    }
219}
220