AppCompatDrawableManager.java revision a59882b82ecd74c79e05538a0962646d003d4c0e
1469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes/*
2469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes * Copyright (C) 2014 The Android Open Source Project
3469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes *
4469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes * Licensed under the Apache License, Version 2.0 (the "License");
5469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes * you may not use this file except in compliance with the License.
6469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes * You may obtain a copy of the License at
7469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes *
8469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes *      http://www.apache.org/licenses/LICENSE-2.0
9469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes *
10469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes * Unless required by applicable law or agreed to in writing, software
11469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes * distributed under the License is distributed on an "AS IS" BASIS,
12469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes * See the License for the specific language governing permissions and
14469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes * limitations under the License.
15469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes */
16469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
1766698bb15ba0f873aa1c2290cc50d6bb839a474aChris Banespackage android.support.v7.widget;
18469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
19e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banesimport org.xmlpull.v1.XmlPullParser;
20e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banesimport org.xmlpull.v1.XmlPullParserException;
21e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes
22469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banesimport android.content.Context;
23469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banesimport android.content.res.ColorStateList;
24e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banesimport android.content.res.Resources;
2539cb4c1c59fa156ed28bc8835ef05eeb356ad13cChris Banesimport android.graphics.Color;
26469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banesimport android.graphics.PorterDuff;
27469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banesimport android.graphics.PorterDuffColorFilter;
28469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banesimport android.graphics.drawable.Drawable;
299f63864c84e3d48841d553521468c7a27189c4f0Chris Banesimport android.graphics.drawable.Drawable.ConstantState;
30cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banesimport android.graphics.drawable.LayerDrawable;
3160cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banesimport android.graphics.drawable.StateListDrawable;
32fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banesimport android.os.Build;
33a1a9092aba48f3ca6400ff53f5f4734752180e81Chris Banesimport android.support.annotation.ColorInt;
347e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banesimport android.support.annotation.DrawableRes;
357e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banesimport android.support.annotation.NonNull;
367e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banesimport android.support.annotation.Nullable;
37bd3494de1d97206f366f52641a14b21eb06a7022Chris Banesimport android.support.graphics.drawable.AnimatedVectorDrawableCompat;
38e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banesimport android.support.graphics.drawable.VectorDrawableCompat;
39469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banesimport android.support.v4.content.ContextCompat;
40eb0d0c030a15e93f456cc1403fffb909c0ae4e66Chris Banesimport android.support.v4.graphics.ColorUtils;
417e82b99953680915596eaf0eb35927388e574ca8Chris Banesimport android.support.v4.graphics.drawable.DrawableCompat;
42e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banesimport android.support.v4.util.ArrayMap;
439f63864c84e3d48841d553521468c7a27189c4f0Chris Banesimport android.support.v4.util.LongSparseArray;
44469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banesimport android.support.v4.util.LruCache;
45469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banesimport android.support.v7.appcompat.R;
465fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banesimport android.support.v7.widget.VectorEnabledTintResources;
47e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banesimport android.util.AttributeSet;
48469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banesimport android.util.Log;
4990075479814eb758d97b822606b448e1a521c298Chris Banesimport android.util.SparseArray;
50e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banesimport android.util.TypedValue;
51e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banesimport android.util.Xml;
52469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
539f63864c84e3d48841d553521468c7a27189c4f0Chris Banesimport java.lang.ref.WeakReference;
54cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banesimport java.util.WeakHashMap;
55cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes
5615aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banesimport static android.support.v7.content.res.AppCompatResources.getColorStateList;
5766698bb15ba0f873aa1c2290cc50d6bb839a474aChris Banesimport static android.support.v7.widget.ThemeUtils.getDisabledThemeAttrColor;
5866698bb15ba0f873aa1c2290cc50d6bb839a474aChris Banesimport static android.support.v7.widget.ThemeUtils.getThemeAttrColor;
5966698bb15ba0f873aa1c2290cc50d6bb839a474aChris Banesimport static android.support.v7.widget.ThemeUtils.getThemeAttrColorStateList;
602aeb0f4237bca35d7f650c3145354416306d4f7bChris Banes
61469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes/**
62469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes * @hide
63469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes */
647e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banespublic final class AppCompatDrawableManager {
657e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes
669f63864c84e3d48841d553521468c7a27189c4f0Chris Banes    private interface InflateDelegate {
67bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes        Drawable createFromXmlInner(@NonNull Context context, @NonNull XmlPullParser parser,
68e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                @NonNull AttributeSet attrs, @Nullable Resources.Theme theme);
697e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes    }
70fd1eb27a3700de31507de34fd1bcc51830fe876cChris Banes
719f63864c84e3d48841d553521468c7a27189c4f0Chris Banes    private static final String TAG = "AppCompatDrawableManager";
72469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes    private static final boolean DEBUG = false;
73cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes    private static final PorterDuff.Mode DEFAULT_MODE = PorterDuff.Mode.SRC_IN;
74e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes    private static final String SKIP_DRAWABLE_TAG = "appcompat_skip_skip";
75415f740df4981ef2f5fb462a50c7cf095cc21128Chris Banes
76ba13a44cd6db80ec983b1ed678b7dbeab04056a1Chris Banes    private static final String PLATFORM_VD_CLAZZ = "android.graphics.drawable.VectorDrawable";
77ba13a44cd6db80ec983b1ed678b7dbeab04056a1Chris Banes
787e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes    private static AppCompatDrawableManager INSTANCE;
797e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes
807e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes    public static AppCompatDrawableManager get() {
817e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes        if (INSTANCE == null) {
827e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes            INSTANCE = new AppCompatDrawableManager();
83bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes            installDefaultInflateDelegates(INSTANCE);
84bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes        }
85bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes        return INSTANCE;
86bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes    }
87bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes
88bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes    private static void installDefaultInflateDelegates(@NonNull AppCompatDrawableManager manager) {
89bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes        final int sdk = Build.VERSION.SDK_INT;
9080e3ecf1aae0badd8056206db0614e9b9d1934b5Teng-Hui Zhu        if (sdk < 23) {
91bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes            // We only want to use the automatic VectorDrawableCompat handling where it's
92bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes            // needed: on devices running before Lollipop
93bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes            manager.addDelegate("vector", new VdcInflateDelegate());
949f63864c84e3d48841d553521468c7a27189c4f0Chris Banes
95bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes            if (sdk >= 11) {
96bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes                // AnimatedVectorDrawableCompat only works on API v11+
97bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes                manager.addDelegate("animated-vector", new AvdcInflateDelegate());
989f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            }
997e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes        }
1007e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes    }
1017e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes
102469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes    private static final ColorFilterLruCache COLOR_FILTER_CACHE = new ColorFilterLruCache(6);
103469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
104469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes    /**
105469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes     * Drawables which should be tinted with the value of {@code R.attr.colorControlNormal},
106cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes     * using the default mode using a raw color filter.
107cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes     */
108cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes    private static final int[] COLORFILTER_TINT_COLOR_CONTROL_NORMAL = {
109cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes            R.drawable.abc_textfield_search_default_mtrl_alpha,
110cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes            R.drawable.abc_textfield_default_mtrl_alpha,
111cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes            R.drawable.abc_ab_share_pack_mtrl_alpha
112cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes    };
113cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes
114cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes    /**
115cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes     * Drawables which should be tinted with the value of {@code R.attr.colorControlNormal}, using
116cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes     * {@link DrawableCompat}'s tinting functionality.
117469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes     */
118469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes    private static final int[] TINT_COLOR_CONTROL_NORMAL = {
119469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            R.drawable.abc_ic_commit_search_api_mtrl_alpha,
120a59882b82ecd74c79e05538a0962646d003d4c0eChris Banes            R.drawable.abc_seekbar_tick_mark_material,
121a59882b82ecd74c79e05538a0962646d003d4c0eChris Banes            R.drawable.abc_ic_menu_share_mtrl_alpha,
122a59882b82ecd74c79e05538a0962646d003d4c0eChris Banes            R.drawable.abc_ic_menu_copy_mtrl_am_alpha,
123a59882b82ecd74c79e05538a0962646d003d4c0eChris Banes            R.drawable.abc_ic_menu_cut_mtrl_alpha,
124a59882b82ecd74c79e05538a0962646d003d4c0eChris Banes            R.drawable.abc_ic_menu_selectall_mtrl_alpha,
125a59882b82ecd74c79e05538a0962646d003d4c0eChris Banes            R.drawable.abc_ic_menu_paste_mtrl_am_alpha
126469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes    };
127469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
128469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes    /**
129469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes     * Drawables which should be tinted with the value of {@code R.attr.colorControlActivated},
130cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes     * using a color filter.
131469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes     */
132cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes    private static final int[] COLORFILTER_COLOR_CONTROL_ACTIVATED = {
133469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            R.drawable.abc_textfield_activated_mtrl_alpha,
13414f8f0dfae09445074dc0f7b5bbe5732d6922da7Chris Banes            R.drawable.abc_textfield_search_activated_mtrl_alpha,
135911642499da7d796aa1e7c19178c3552a590f48bChris Banes            R.drawable.abc_cab_background_top_mtrl_alpha,
136f65da421a61773f1ce03550230dcd6f58cee54e9Chris Banes            R.drawable.abc_text_cursor_material
137469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes    };
138469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
139469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes    /**
140469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes     * Drawables which should be tinted with the value of {@code android.R.attr.colorBackground},
141cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes     * using the {@link android.graphics.PorterDuff.Mode#MULTIPLY} mode and a color filter.
142469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes     */
143cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes    private static final int[] COLORFILTER_COLOR_BACKGROUND_MULTIPLY = {
1441073132946bb0a53a788949fe4c060f72051cd57Chris Banes            R.drawable.abc_popup_background_mtrl_mult,
14557c6de90985a63358129b99b9f0cd4d6afe887d6Chris Banes            R.drawable.abc_cab_background_internal_bg,
14657c6de90985a63358129b99b9f0cd4d6afe887d6Chris Banes            R.drawable.abc_menu_hardkey_panel_mtrl_mult
147469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes    };
148469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
149469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes    /**
150469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes     * Drawables which should be tinted using a state list containing values of
151469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes     * {@code R.attr.colorControlNormal} and {@code R.attr.colorControlActivated}
152469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes     */
153469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes    private static final int[] TINT_COLOR_CONTROL_STATE_LIST = {
154469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            R.drawable.abc_tab_indicator_material,
15574433e220541f8236f4e01f658a96854b29acfd1Chris Banes            R.drawable.abc_textfield_search_material
156469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes    };
157469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
1581752dfd10e8242d6e017b3828c7d6e94f044691cChris Banes    /**
1591752dfd10e8242d6e017b3828c7d6e94f044691cChris Banes     * Drawables which should be tinted using a state list containing values of
1601752dfd10e8242d6e017b3828c7d6e94f044691cChris Banes     * {@code R.attr.colorControlNormal} and {@code R.attr.colorControlActivated} for the checked
1611752dfd10e8242d6e017b3828c7d6e94f044691cChris Banes     * state.
1621752dfd10e8242d6e017b3828c7d6e94f044691cChris Banes     */
1631752dfd10e8242d6e017b3828c7d6e94f044691cChris Banes    private static final int[] TINT_CHECKABLE_BUTTON_LIST = {
1641752dfd10e8242d6e017b3828c7d6e94f044691cChris Banes            R.drawable.abc_btn_check_material,
1651752dfd10e8242d6e017b3828c7d6e94f044691cChris Banes            R.drawable.abc_btn_radio_material
1661752dfd10e8242d6e017b3828c7d6e94f044691cChris Banes    };
1671752dfd10e8242d6e017b3828c7d6e94f044691cChris Banes
1687e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes    private WeakHashMap<Context, SparseArray<ColorStateList>> mTintLists;
169e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes    private ArrayMap<String, InflateDelegate> mDelegates;
170e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes    private SparseArray<String> mKnownDrawableIdTags;
171e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes
17260cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes    private final Object mDrawableCacheLock = new Object();
1739f63864c84e3d48841d553521468c7a27189c4f0Chris Banes    private final WeakHashMap<Context, LongSparseArray<WeakReference<Drawable.ConstantState>>>
17460cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes            mDrawableCaches = new WeakHashMap<>(0);
1759f63864c84e3d48841d553521468c7a27189c4f0Chris Banes
176e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes    private TypedValue mTypedValue;
177469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
178ba13a44cd6db80ec983b1ed678b7dbeab04056a1Chris Banes    private boolean mHasCheckedVectorDrawableSetup;
179ba13a44cd6db80ec983b1ed678b7dbeab04056a1Chris Banes
1807e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes    public Drawable getDrawable(@NonNull Context context, @DrawableRes int resId) {
1817e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes        return getDrawable(context, resId, false);
182469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes    }
183469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
1847e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes    public Drawable getDrawable(@NonNull Context context, @DrawableRes int resId,
1857e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes            boolean failIfNotKnown) {
186eb648620b291eabbb5523f649baef5abb2e4687bAlan Viverette        checkVectorDrawableSetup(context);
187eb648620b291eabbb5523f649baef5abb2e4687bAlan Viverette
188e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes        Drawable drawable = loadDrawableFromDelegates(context, resId);
189e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes        if (drawable == null) {
19060cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes            drawable = createDrawableIfNeeded(context, resId);
19160cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes        }
19260cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes        if (drawable == null) {
193e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            drawable = ContextCompat.getDrawable(context, resId);
194cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes        }
19560cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes
196e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes        if (drawable != null) {
197aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes            // Tint it if needed
198aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes            drawable = tintDrawable(context, resId, failIfNotKnown, drawable);
199cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes        }
200aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes        if (drawable != null) {
201aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes            // See if we need to 'fix' the drawable
202aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes            DrawableUtils.fixDrawable(drawable);
203aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes        }
204aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes        return drawable;
205e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes    }
206cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes
20760cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes    private static long createCacheKey(TypedValue tv) {
20860cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes        return (((long) tv.assetCookie) << 32) | tv.data;
20960cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes    }
21060cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes
21160cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes    private Drawable createDrawableIfNeeded(@NonNull Context context,
21260cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes            @DrawableRes final int resId) {
21360cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes        if (mTypedValue == null) {
21460cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes            mTypedValue = new TypedValue();
21560cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes        }
21660cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes        final TypedValue tv = mTypedValue;
21760cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes        context.getResources().getValue(resId, tv, true);
21860cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes        final long key = createCacheKey(tv);
21960cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes
22060cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes        Drawable dr = getCachedDrawable(context, key);
22160cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes        if (dr != null) {
22260cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes            // If we got a cached drawable, return it
22360cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes            return dr;
22460cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes        }
22560cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes
22660cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes        // Else we need to try and create one...
22760cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes        if (resId == R.drawable.abc_cab_background_top_material) {
22860cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes            dr = new LayerDrawable(new Drawable[]{
22960cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes                    getDrawable(context, R.drawable.abc_cab_background_internal_bg),
23060cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes                    getDrawable(context, R.drawable.abc_cab_background_top_mtrl_alpha)
23160cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes            });
23260cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes        }
23360cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes
23460cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes        if (dr != null) {
23560cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes            dr.setChangingConfigurations(tv.changingConfigurations);
23660cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes            // If we reached here then we created a new drawable, add it to the cache
23760cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes            addDrawableToCache(context, key, dr);
23860cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes        }
23960cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes
24060cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes        return dr;
24160cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes    }
24260cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes
243e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes    private Drawable tintDrawable(@NonNull Context context, @DrawableRes int resId,
244e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            boolean failIfNotKnown, @NonNull Drawable drawable) {
245e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes        final ColorStateList tintList = getTintList(context, resId);
246e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes        if (tintList != null) {
247e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            // First mutate the Drawable, then wrap it and set the tint list
248aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes            if (DrawableUtils.canSafelyMutateDrawable(drawable)) {
249e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                drawable = drawable.mutate();
250e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            }
251e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            drawable = DrawableCompat.wrap(drawable);
252e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            DrawableCompat.setTintList(drawable, tintList);
253469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
254e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            // If there is a blending mode specified for the drawable, use it
255e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            final PorterDuff.Mode tintMode = getTintMode(resId);
256e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            if (tintMode != null) {
257e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                DrawableCompat.setTintMode(drawable, tintMode);
258e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            }
259e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes        } else if (resId == R.drawable.abc_seekbar_track_material) {
260e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            LayerDrawable ld = (LayerDrawable) drawable;
261e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.background),
262e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    getThemeAttrColor(context, R.attr.colorControlNormal), DEFAULT_MODE);
263e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.secondaryProgress),
264e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    getThemeAttrColor(context, R.attr.colorControlNormal), DEFAULT_MODE);
265e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.progress),
266e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    getThemeAttrColor(context, R.attr.colorControlActivated), DEFAULT_MODE);
26774433e220541f8236f4e01f658a96854b29acfd1Chris Banes        } else if (resId == R.drawable.abc_ratingbar_material
26874433e220541f8236f4e01f658a96854b29acfd1Chris Banes                || resId == R.drawable.abc_ratingbar_indicator_material
269e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                || resId == R.drawable.abc_ratingbar_small_material) {
270e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            LayerDrawable ld = (LayerDrawable) drawable;
271e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.background),
272e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    getDisabledThemeAttrColor(context, R.attr.colorControlNormal),
273e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    DEFAULT_MODE);
274e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.secondaryProgress),
275e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    getThemeAttrColor(context, R.attr.colorControlActivated), DEFAULT_MODE);
276e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.progress),
277e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    getThemeAttrColor(context, R.attr.colorControlActivated), DEFAULT_MODE);
278e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes        } else {
279e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            final boolean tinted = tintDrawableUsingColorFilter(context, resId, drawable);
280e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            if (!tinted && failIfNotKnown) {
281e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                // If we didn't tint using a ColorFilter, and we're set to fail if we don't
282e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                // know the id, return null
283e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                drawable = null;
284e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            }
285e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes        }
286e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes        return drawable;
287e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes    }
288a5f106fbd09335ae504c39b1ee1e0caa3f1238e3Chris Banes
289e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes    private Drawable loadDrawableFromDelegates(@NonNull Context context, @DrawableRes int resId) {
290e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes        if (mDelegates != null && !mDelegates.isEmpty()) {
291e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            if (mKnownDrawableIdTags != null) {
2929f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                final String cachedTagName = mKnownDrawableIdTags.get(resId);
293e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                if (SKIP_DRAWABLE_TAG.equals(cachedTagName)
294e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                        || (cachedTagName != null && mDelegates.get(cachedTagName) == null)) {
295e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    // If we don't have a delegate for the drawable tag, or we've been set to
296e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    // skip it, fail fast and return null
297e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    if (DEBUG) {
2989f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                        Log.d(TAG, "[loadDrawableFromDelegates] Skipping drawable: "
299e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                                + context.getResources().getResourceName(resId));
300e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    }
301e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    return null;
302a5f106fbd09335ae504c39b1ee1e0caa3f1238e3Chris Banes                }
303469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            } else {
304e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                // Create an id cache as we'll need one later
305e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                mKnownDrawableIdTags = new SparseArray<>();
306e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            }
307e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes
308e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            if (mTypedValue == null) {
309e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                mTypedValue = new TypedValue();
310e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            }
311e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            final TypedValue tv = mTypedValue;
312e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            final Resources res = context.getResources();
313e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            res.getValue(resId, tv, true);
314e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes
31560cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes            final long key = createCacheKey(tv);
3169f63864c84e3d48841d553521468c7a27189c4f0Chris Banes
31760cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes            Drawable dr = getCachedDrawable(context, key);
3189f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            if (dr != null) {
3199f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                if (DEBUG) {
3209f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                    Log.i(TAG, "[loadDrawableFromDelegates] Returning cached drawable: " +
3219f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                            context.getResources().getResourceName(resId));
3229f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                }
3239f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                // We have a cached drawable, return it!
3249f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                return dr;
3259f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            }
3269f63864c84e3d48841d553521468c7a27189c4f0Chris Banes
327e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            if (tv.string != null && tv.string.toString().endsWith(".xml")) {
328e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                // If the resource is an XML file, let's try and parse it
329e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                try {
330e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    final XmlPullParser parser = res.getXml(resId);
331e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    final AttributeSet attrs = Xml.asAttributeSet(parser);
332e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    int type;
333e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    while ((type = parser.next()) != XmlPullParser.START_TAG &&
334e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                            type != XmlPullParser.END_DOCUMENT) {
335e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                        // Empty loop
336e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    }
337e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    if (type != XmlPullParser.START_TAG) {
338e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                        throw new XmlPullParserException("No start tag found");
339e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    }
340e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes
341e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    final String tagName = parser.getName();
3429f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                    // Add the tag name to the cache
3439f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                    mKnownDrawableIdTags.append(resId, tagName);
344e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes
345e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    // Now try and find a delegate for the tag name and inflate if found
346e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    final InflateDelegate delegate = mDelegates.get(tagName);
347e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    if (delegate != null) {
348bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes                        dr = delegate.createFromXmlInner(context, parser, attrs,
349bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes                                context.getTheme());
3509f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                    }
3519f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                    if (dr != null) {
3529f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                        // Add it to the drawable cache
3539f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                        dr.setChangingConfigurations(tv.changingConfigurations);
35460cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes                        if (addDrawableToCache(context, key, dr) && DEBUG) {
3559f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                            Log.i(TAG, "[loadDrawableFromDelegates] Saved drawable to cache: " +
3569f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                                    context.getResources().getResourceName(resId));
3579f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                        }
358e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    }
359e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                } catch (Exception e) {
360e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes                    Log.e(TAG, "Exception while inflating drawable", e);
3614ab820f4155444d20b37e105873775dd71907eefChris Banes                }
362469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            }
3639f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            if (dr == null) {
3649f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                // If we reach here then the delegate inflation of the resource failed. Mark it as
3659f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                // bad so we skip the id next time
3669f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                mKnownDrawableIdTags.append(resId, SKIP_DRAWABLE_TAG);
3679f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            }
3689f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            return dr;
369469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes        }
370e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes
371e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes        return null;
372469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes    }
373469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
37460cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes    private Drawable getCachedDrawable(@NonNull final Context context, final long key) {
37560cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes        synchronized (mDrawableCacheLock) {
3769f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            final LongSparseArray<WeakReference<ConstantState>> cache
37760cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes                    = mDrawableCaches.get(context);
3789f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            if (cache == null) {
3799f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                return null;
3809f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            }
3819f63864c84e3d48841d553521468c7a27189c4f0Chris Banes
3829f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            final WeakReference<ConstantState> wr = cache.get(key);
3839f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            if (wr != null) {
3849f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                // We have the key, and the secret
3859f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                ConstantState entry = wr.get();
3869f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                if (entry != null) {
3879f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                    return entry.newDrawable(context.getResources());
3889f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                } else {
3899f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                    // Our entry has been purged
3909f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                    cache.delete(key);
3919f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                }
3929f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            }
3939f63864c84e3d48841d553521468c7a27189c4f0Chris Banes        }
3949f63864c84e3d48841d553521468c7a27189c4f0Chris Banes        return null;
3959f63864c84e3d48841d553521468c7a27189c4f0Chris Banes    }
3969f63864c84e3d48841d553521468c7a27189c4f0Chris Banes
39760cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes    private boolean addDrawableToCache(@NonNull final Context context, final long key,
3989f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            @NonNull final Drawable drawable) {
3999f63864c84e3d48841d553521468c7a27189c4f0Chris Banes        final ConstantState cs = drawable.getConstantState();
4009f63864c84e3d48841d553521468c7a27189c4f0Chris Banes        if (cs != null) {
40160cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes            synchronized (mDrawableCacheLock) {
40260cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes                LongSparseArray<WeakReference<ConstantState>> cache = mDrawableCaches.get(context);
4039f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                if (cache == null) {
4049f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                    cache = new LongSparseArray<>();
40560cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes                    mDrawableCaches.put(context, cache);
4069f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                }
4079f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                cache.put(key, new WeakReference<ConstantState>(cs));
4089f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            }
4099f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            return true;
4109f63864c84e3d48841d553521468c7a27189c4f0Chris Banes        }
4119f63864c84e3d48841d553521468c7a27189c4f0Chris Banes        return false;
4129f63864c84e3d48841d553521468c7a27189c4f0Chris Banes    }
4139f63864c84e3d48841d553521468c7a27189c4f0Chris Banes
4145fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes    public final Drawable onDrawableLoadedFromResources(@NonNull Context context,
4155fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes            @NonNull VectorEnabledTintResources resources, @DrawableRes final int resId) {
4165fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes        Drawable drawable = loadDrawableFromDelegates(context, resId);
4175fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes        if (drawable == null) {
4185fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes            drawable = resources.superGetDrawable(resId);
4195fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes        }
4205fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes        if (drawable != null) {
4215fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes            return tintDrawable(context, resId, false, drawable);
4225fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes        }
4235fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes        return null;
4245fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes    }
4255fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes
42660cc94cdf2f9dfd10f2fe0b3fa0fa438d51df271Chris Banes    static boolean tintDrawableUsingColorFilter(@NonNull Context context,
4277e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes            @DrawableRes final int resId, @NonNull Drawable drawable) {
42839cb4c1c59fa156ed28bc8835ef05eeb356ad13cChris Banes        PorterDuff.Mode tintMode = DEFAULT_MODE;
429469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes        boolean colorAttrSet = false;
430469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes        int colorAttr = 0;
4310517b282bde8b9a0377dfe5bc4756405a196adb4Chris Banes        int alpha = -1;
432469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
433cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes        if (arrayContains(COLORFILTER_TINT_COLOR_CONTROL_NORMAL, resId)) {
434469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            colorAttr = R.attr.colorControlNormal;
435469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            colorAttrSet = true;
436cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes        } else if (arrayContains(COLORFILTER_COLOR_CONTROL_ACTIVATED, resId)) {
437469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            colorAttr = R.attr.colorControlActivated;
438469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            colorAttrSet = true;
439cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes        } else if (arrayContains(COLORFILTER_COLOR_BACKGROUND_MULTIPLY, resId)) {
440469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            colorAttr = android.R.attr.colorBackground;
441469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            colorAttrSet = true;
442469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            tintMode = PorterDuff.Mode.MULTIPLY;
4430517b282bde8b9a0377dfe5bc4756405a196adb4Chris Banes        } else if (resId == R.drawable.abc_list_divider_mtrl_alpha) {
4440517b282bde8b9a0377dfe5bc4756405a196adb4Chris Banes            colorAttr = android.R.attr.colorForeground;
4450517b282bde8b9a0377dfe5bc4756405a196adb4Chris Banes            colorAttrSet = true;
4460517b282bde8b9a0377dfe5bc4756405a196adb4Chris Banes            alpha = Math.round(0.16f * 255);
447593d8bcc82c01a559f737dfcbc6e806005300a64Chris Banes        } else if (resId == R.drawable.abc_dialog_material_background) {
448593d8bcc82c01a559f737dfcbc6e806005300a64Chris Banes            colorAttr = android.R.attr.colorBackground;
449593d8bcc82c01a559f737dfcbc6e806005300a64Chris Banes            colorAttrSet = true;
450469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes        }
451469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
452f31fb9d2fd3b5b130f3f5ac121b033546d869231Chris Banes        if (colorAttrSet) {
453aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes            if (DrawableUtils.canSafelyMutateDrawable(drawable)) {
454d20d889e7abef50efbaf6e975100a8fb73409b13Chris Banes                drawable = drawable.mutate();
455d20d889e7abef50efbaf6e975100a8fb73409b13Chris Banes            }
456d20d889e7abef50efbaf6e975100a8fb73409b13Chris Banes
457cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes            final int color = getThemeAttrColor(context, colorAttr);
45839cb4c1c59fa156ed28bc8835ef05eeb356ad13cChris Banes            drawable.setColorFilter(getPorterDuffColorFilter(color, tintMode));
459469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
4600517b282bde8b9a0377dfe5bc4756405a196adb4Chris Banes            if (alpha != -1) {
4610517b282bde8b9a0377dfe5bc4756405a196adb4Chris Banes                drawable.setAlpha(alpha);
4620517b282bde8b9a0377dfe5bc4756405a196adb4Chris Banes            }
4630517b282bde8b9a0377dfe5bc4756405a196adb4Chris Banes
464469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            if (DEBUG) {
4659f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                Log.d(TAG, "[tintDrawableUsingColorFilter] Tinted "
4669f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                        + context.getResources().getResourceName(resId) +
467469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes                        " with color: #" + Integer.toHexString(color));
468469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            }
4694ab820f4155444d20b37e105873775dd71907eefChris Banes            return true;
470469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes        }
4714ab820f4155444d20b37e105873775dd71907eefChris Banes        return false;
472469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes    }
473469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
4749f63864c84e3d48841d553521468c7a27189c4f0Chris Banes    private void addDelegate(@NonNull String tagName, @NonNull InflateDelegate delegate) {
4757e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes        if (mDelegates == null) {
476e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            mDelegates = new ArrayMap<>();
4777e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes        }
478e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes        mDelegates.put(tagName, delegate);
4797e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes    }
4807e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes
4819f63864c84e3d48841d553521468c7a27189c4f0Chris Banes    private void removeDelegate(@NonNull String tagName, @NonNull InflateDelegate delegate) {
482e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes        if (mDelegates != null && mDelegates.get(tagName) == delegate) {
483e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes            mDelegates.remove(tagName);
4847e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes        }
4857e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes    }
4867e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes
487469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes    private static boolean arrayContains(int[] array, int value) {
488469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes        for (int id : array) {
489469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            if (id == value) {
490469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes                return true;
491469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            }
492469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes        }
493469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes        return false;
494469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes    }
495469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
496cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes    final PorterDuff.Mode getTintMode(final int resId) {
497cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes        PorterDuff.Mode mode = null;
498cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes
499cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes        if (resId == R.drawable.abc_switch_thumb_material) {
500cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes            mode = PorterDuff.Mode.MULTIPLY;
501cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes        }
502cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes
503cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes        return mode;
504a5f106fbd09335ae504c39b1ee1e0caa3f1238e3Chris Banes    }
505a5f106fbd09335ae504c39b1ee1e0caa3f1238e3Chris Banes
5067e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes    public final ColorStateList getTintList(@NonNull Context context, @DrawableRes int resId) {
507cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes        // Try the cache first (if it exists)
5087e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes        ColorStateList tint = getTintListFromCache(context, resId);
509cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes
510cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes        if (tint == null) {
511cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes            // ...if the cache did not contain a color state list, try and create one
51290075479814eb758d97b822606b448e1a521c298Chris Banes            if (resId == R.drawable.abc_edit_text_material) {
5139d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                tint = getColorStateList(context, R.color.abc_tint_edittext);
51490075479814eb758d97b822606b448e1a521c298Chris Banes            } else if (resId == R.drawable.abc_switch_track_mtrl_alpha) {
5159d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                tint = getColorStateList(context, R.color.abc_tint_switch_track);
51690075479814eb758d97b822606b448e1a521c298Chris Banes            } else if (resId == R.drawable.abc_switch_thumb_material) {
5179d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                tint = getColorStateList(context, R.color.abc_tint_switch_thumb);
518a1a9092aba48f3ca6400ff53f5f4734752180e81Chris Banes            } else if (resId == R.drawable.abc_btn_default_mtrl_shape) {
519b1131c6dfc9affe5751523f235878055cb699960Chris Banes                tint = createDefaultButtonColorStateList(context);
520a1a9092aba48f3ca6400ff53f5f4734752180e81Chris Banes            } else if (resId == R.drawable.abc_btn_borderless_material) {
521a1a9092aba48f3ca6400ff53f5f4734752180e81Chris Banes                tint = createBorderlessButtonColorStateList(context);
522b1131c6dfc9affe5751523f235878055cb699960Chris Banes            } else if (resId == R.drawable.abc_btn_colored_material) {
523b1131c6dfc9affe5751523f235878055cb699960Chris Banes                tint = createColoredButtonColorStateList(context);
524cdd1b1d70cefeb052c2b506738b396f2f982e519Chris Banes            } else if (resId == R.drawable.abc_spinner_mtrl_am_alpha
525cdd1b1d70cefeb052c2b506738b396f2f982e519Chris Banes                    || resId == R.drawable.abc_spinner_textfield_background_material) {
5269d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                tint = getColorStateList(context, R.color.abc_tint_spinner);
527cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes            } else if (arrayContains(TINT_COLOR_CONTROL_NORMAL, resId)) {
528cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes                tint = getThemeAttrColorStateList(context, R.attr.colorControlNormal);
529cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes            } else if (arrayContains(TINT_COLOR_CONTROL_STATE_LIST, resId)) {
5309d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                tint = getColorStateList(context, R.color.abc_tint_default);
5311752dfd10e8242d6e017b3828c7d6e94f044691cChris Banes            } else if (arrayContains(TINT_CHECKABLE_BUTTON_LIST, resId)) {
5329d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                tint = getColorStateList(context, R.color.abc_tint_btn_checkable);
53310e2dbc1ad1c01d2824d921a8b0f070859d6f146Chris Banes            } else if (resId == R.drawable.abc_seekbar_thumb_material) {
5349d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes                tint = getColorStateList(context, R.color.abc_tint_seek_thumb);
53590075479814eb758d97b822606b448e1a521c298Chris Banes            }
536a5f106fbd09335ae504c39b1ee1e0caa3f1238e3Chris Banes
537cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes            if (tint != null) {
5387e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes                addTintListToCache(context, resId, tint);
539cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes            }
54090075479814eb758d97b822606b448e1a521c298Chris Banes        }
541cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes        return tint;
54290075479814eb758d97b822606b448e1a521c298Chris Banes    }
54390075479814eb758d97b822606b448e1a521c298Chris Banes
5447e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes    private ColorStateList getTintListFromCache(@NonNull Context context, @DrawableRes int resId) {
5457e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes        if (mTintLists != null) {
5467e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes            final SparseArray<ColorStateList> tints = mTintLists.get(context);
5477e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes            return tints != null ? tints.get(resId) : null;
5487e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes        }
5497e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes        return null;
5507e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes    }
551469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
5527e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes    private void addTintListToCache(@NonNull Context context, @DrawableRes int resId,
5537e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes            @NonNull ColorStateList tintList) {
5547e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes        if (mTintLists == null) {
5557e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes            mTintLists = new WeakHashMap<>();
5567e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes        }
5577e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes        SparseArray<ColorStateList> themeTints = mTintLists.get(context);
5587e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes        if (themeTints == null) {
5597e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes            themeTints = new SparseArray<>();
5607e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes            mTintLists.put(context, themeTints);
5617e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes        }
5627e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes        themeTints.append(resId, tintList);
5637e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes    }
564469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
565b1131c6dfc9affe5751523f235878055cb699960Chris Banes    private ColorStateList createDefaultButtonColorStateList(Context context) {
566a1a9092aba48f3ca6400ff53f5f4734752180e81Chris Banes        return createButtonColorStateList(context,
567a1a9092aba48f3ca6400ff53f5f4734752180e81Chris Banes                getThemeAttrColor(context, R.attr.colorButtonNormal));
568a1a9092aba48f3ca6400ff53f5f4734752180e81Chris Banes    }
569a1a9092aba48f3ca6400ff53f5f4734752180e81Chris Banes
570a1a9092aba48f3ca6400ff53f5f4734752180e81Chris Banes    private ColorStateList createBorderlessButtonColorStateList(Context context) {
571a1a9092aba48f3ca6400ff53f5f4734752180e81Chris Banes        return createButtonColorStateList(context, Color.TRANSPARENT);
572b1131c6dfc9affe5751523f235878055cb699960Chris Banes    }
573b1131c6dfc9affe5751523f235878055cb699960Chris Banes
574b1131c6dfc9affe5751523f235878055cb699960Chris Banes    private ColorStateList createColoredButtonColorStateList(Context context) {
575a1a9092aba48f3ca6400ff53f5f4734752180e81Chris Banes        return createButtonColorStateList(context, getThemeAttrColor(context, R.attr.colorAccent));
576b1131c6dfc9affe5751523f235878055cb699960Chris Banes    }
577b1131c6dfc9affe5751523f235878055cb699960Chris Banes
578a1a9092aba48f3ca6400ff53f5f4734752180e81Chris Banes    private ColorStateList createButtonColorStateList(Context context, @ColorInt int baseColor) {
57990075479814eb758d97b822606b448e1a521c298Chris Banes        final int[][] states = new int[4][];
58090075479814eb758d97b822606b448e1a521c298Chris Banes        final int[] colors = new int[4];
58190075479814eb758d97b822606b448e1a521c298Chris Banes        int i = 0;
58290075479814eb758d97b822606b448e1a521c298Chris Banes
583eb0d0c030a15e93f456cc1403fffb909c0ae4e66Chris Banes        final int colorControlHighlight = getThemeAttrColor(context, R.attr.colorControlHighlight);
584eb0d0c030a15e93f456cc1403fffb909c0ae4e66Chris Banes
58590075479814eb758d97b822606b448e1a521c298Chris Banes        // Disabled state
586bb1a62b3eed874aa47ea4763ac972902c77e988fChris Banes        states[i] = ThemeUtils.DISABLED_STATE_SET;
587cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes        colors[i] = getDisabledThemeAttrColor(context, R.attr.colorButtonNormal);
58890075479814eb758d97b822606b448e1a521c298Chris Banes        i++;
58990075479814eb758d97b822606b448e1a521c298Chris Banes
590bb1a62b3eed874aa47ea4763ac972902c77e988fChris Banes        states[i] = ThemeUtils.PRESSED_STATE_SET;
591b1131c6dfc9affe5751523f235878055cb699960Chris Banes        colors[i] = ColorUtils.compositeColors(colorControlHighlight, baseColor);
59290075479814eb758d97b822606b448e1a521c298Chris Banes        i++;
59390075479814eb758d97b822606b448e1a521c298Chris Banes
594bb1a62b3eed874aa47ea4763ac972902c77e988fChris Banes        states[i] = ThemeUtils.FOCUSED_STATE_SET;
595b1131c6dfc9affe5751523f235878055cb699960Chris Banes        colors[i] = ColorUtils.compositeColors(colorControlHighlight, baseColor);
59690075479814eb758d97b822606b448e1a521c298Chris Banes        i++;
59790075479814eb758d97b822606b448e1a521c298Chris Banes
59890075479814eb758d97b822606b448e1a521c298Chris Banes        // Default enabled state
599bb1a62b3eed874aa47ea4763ac972902c77e988fChris Banes        states[i] = ThemeUtils.EMPTY_STATE_SET;
600b1131c6dfc9affe5751523f235878055cb699960Chris Banes        colors[i] = baseColor;
60190075479814eb758d97b822606b448e1a521c298Chris Banes        i++;
60290075479814eb758d97b822606b448e1a521c298Chris Banes
60390075479814eb758d97b822606b448e1a521c298Chris Banes        return new ColorStateList(states, colors);
604a9585dae398a69dd67797e7ca86f44ffcabd9e86Chris Banes    }
605a9585dae398a69dd67797e7ca86f44ffcabd9e86Chris Banes
606469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes    private static class ColorFilterLruCache extends LruCache<Integer, PorterDuffColorFilter> {
607469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
608469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes        public ColorFilterLruCache(int maxSize) {
609469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            super(maxSize);
610469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes        }
611469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
612469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes        PorterDuffColorFilter get(int color, PorterDuff.Mode mode) {
613469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            return get(generateCacheKey(color, mode));
614469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes        }
615469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
616469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes        PorterDuffColorFilter put(int color, PorterDuff.Mode mode, PorterDuffColorFilter filter) {
617469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            return put(generateCacheKey(color, mode), filter);
618469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes        }
619469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
620469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes        private static int generateCacheKey(int color, PorterDuff.Mode mode) {
621469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            int hashCode = 1;
622469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            hashCode = 31 * hashCode + color;
623469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            hashCode = 31 * hashCode + mode.hashCode();
624469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            return hashCode;
625469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes        }
626469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes    }
627a5f106fbd09335ae504c39b1ee1e0caa3f1238e3Chris Banes
628f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes    public static void tintDrawable(Drawable drawable, TintInfo tint, int[] state) {
629aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes        if (DrawableUtils.canSafelyMutateDrawable(drawable)
630aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes                && drawable.mutate() != drawable) {
63144eb323d09e9bc3baca24ef4a8595aafb7ed2652Chris Banes            Log.d(TAG, "Mutated drawable is not the same instance as the input.");
63244eb323d09e9bc3baca24ef4a8595aafb7ed2652Chris Banes            return;
63344eb323d09e9bc3baca24ef4a8595aafb7ed2652Chris Banes        }
634eb0d0c030a15e93f456cc1403fffb909c0ae4e66Chris Banes
63544eb323d09e9bc3baca24ef4a8595aafb7ed2652Chris Banes        if (tint.mHasTintList || tint.mHasTintMode) {
63644eb323d09e9bc3baca24ef4a8595aafb7ed2652Chris Banes            drawable.setColorFilter(createTintFilter(
63744eb323d09e9bc3baca24ef4a8595aafb7ed2652Chris Banes                    tint.mHasTintList ? tint.mTintList : null,
63844eb323d09e9bc3baca24ef4a8595aafb7ed2652Chris Banes                    tint.mHasTintMode ? tint.mTintMode : DEFAULT_MODE,
63944eb323d09e9bc3baca24ef4a8595aafb7ed2652Chris Banes                    state));
64044eb323d09e9bc3baca24ef4a8595aafb7ed2652Chris Banes        } else {
64144eb323d09e9bc3baca24ef4a8595aafb7ed2652Chris Banes            drawable.clearColorFilter();
64244eb323d09e9bc3baca24ef4a8595aafb7ed2652Chris Banes        }
64344eb323d09e9bc3baca24ef4a8595aafb7ed2652Chris Banes
6447797b9f22c8c404309b778a0966266d2b1a84915Chris Banes        if (Build.VERSION.SDK_INT <= 23) {
6457797b9f22c8c404309b778a0966266d2b1a84915Chris Banes            // Pre-v23 there is no guarantee that a state change will invoke an invalidation,
6467797b9f22c8c404309b778a0966266d2b1a84915Chris Banes            // so we force it ourselves
64744eb323d09e9bc3baca24ef4a8595aafb7ed2652Chris Banes            drawable.invalidateSelf();
64844eb323d09e9bc3baca24ef4a8595aafb7ed2652Chris Banes        }
64944eb323d09e9bc3baca24ef4a8595aafb7ed2652Chris Banes    }
65044eb323d09e9bc3baca24ef4a8595aafb7ed2652Chris Banes
65139cb4c1c59fa156ed28bc8835ef05eeb356ad13cChris Banes    private static PorterDuffColorFilter createTintFilter(ColorStateList tint,
65239cb4c1c59fa156ed28bc8835ef05eeb356ad13cChris Banes            PorterDuff.Mode tintMode, final int[] state) {
65339cb4c1c59fa156ed28bc8835ef05eeb356ad13cChris Banes        if (tint == null || tintMode == null) {
65439cb4c1c59fa156ed28bc8835ef05eeb356ad13cChris Banes            return null;
655cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes        }
65639cb4c1c59fa156ed28bc8835ef05eeb356ad13cChris Banes        final int color = tint.getColorForState(state, Color.TRANSPARENT);
65739cb4c1c59fa156ed28bc8835ef05eeb356ad13cChris Banes        return getPorterDuffColorFilter(color, tintMode);
65839cb4c1c59fa156ed28bc8835ef05eeb356ad13cChris Banes    }
659cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes
660fe1cbed21122206b7a4af97790ade439d49421d8Chris Banes    public static PorterDuffColorFilter getPorterDuffColorFilter(int color, PorterDuff.Mode mode) {
661a5f106fbd09335ae504c39b1ee1e0caa3f1238e3Chris Banes        // First, lets see if the cache already contains the color filter
662a5f106fbd09335ae504c39b1ee1e0caa3f1238e3Chris Banes        PorterDuffColorFilter filter = COLOR_FILTER_CACHE.get(color, mode);
663a5f106fbd09335ae504c39b1ee1e0caa3f1238e3Chris Banes
664a5f106fbd09335ae504c39b1ee1e0caa3f1238e3Chris Banes        if (filter == null) {
665a5f106fbd09335ae504c39b1ee1e0caa3f1238e3Chris Banes            // Cache miss, so create a color filter and add it to the cache
666a5f106fbd09335ae504c39b1ee1e0caa3f1238e3Chris Banes            filter = new PorterDuffColorFilter(color, mode);
667a5f106fbd09335ae504c39b1ee1e0caa3f1238e3Chris Banes            COLOR_FILTER_CACHE.put(color, mode, filter);
668a5f106fbd09335ae504c39b1ee1e0caa3f1238e3Chris Banes        }
669a5f106fbd09335ae504c39b1ee1e0caa3f1238e3Chris Banes
67039cb4c1c59fa156ed28bc8835ef05eeb356ad13cChris Banes        return filter;
671a5f106fbd09335ae504c39b1ee1e0caa3f1238e3Chris Banes    }
67210e2dbc1ad1c01d2824d921a8b0f070859d6f146Chris Banes
67310e2dbc1ad1c01d2824d921a8b0f070859d6f146Chris Banes    private static void setPorterDuffColorFilter(Drawable d, int color, PorterDuff.Mode mode) {
674aaa85b7d563d27fdf10048dd619a317451477ad5Chris Banes        if (DrawableUtils.canSafelyMutateDrawable(d)) {
675d20d889e7abef50efbaf6e975100a8fb73409b13Chris Banes            d = d.mutate();
676d20d889e7abef50efbaf6e975100a8fb73409b13Chris Banes        }
67710e2dbc1ad1c01d2824d921a8b0f070859d6f146Chris Banes        d.setColorFilter(getPorterDuffColorFilter(color, mode == null ? DEFAULT_MODE : mode));
67810e2dbc1ad1c01d2824d921a8b0f070859d6f146Chris Banes    }
6799f63864c84e3d48841d553521468c7a27189c4f0Chris Banes
680eb648620b291eabbb5523f649baef5abb2e4687bAlan Viverette    private void checkVectorDrawableSetup(@NonNull Context context) {
681eb648620b291eabbb5523f649baef5abb2e4687bAlan Viverette        if (!mHasCheckedVectorDrawableSetup) {
682eb648620b291eabbb5523f649baef5abb2e4687bAlan Viverette            // We've already checked so return now...
683eb648620b291eabbb5523f649baef5abb2e4687bAlan Viverette            return;
684eb648620b291eabbb5523f649baef5abb2e4687bAlan Viverette        }
685eb648620b291eabbb5523f649baef5abb2e4687bAlan Viverette        // Here we will check that a known Vector drawable resource inside AppCompat can be
686eb648620b291eabbb5523f649baef5abb2e4687bAlan Viverette        // correctly decoded. We use one that will almost definitely be used in the future to
687eb648620b291eabbb5523f649baef5abb2e4687bAlan Viverette        // negate any wasted work
688eb648620b291eabbb5523f649baef5abb2e4687bAlan Viverette        final Drawable d = getDrawable(context, R.drawable.abc_ic_ab_back_material);
689eb648620b291eabbb5523f649baef5abb2e4687bAlan Viverette        if (d != null && isVectorDrawable(d)) {
690eb648620b291eabbb5523f649baef5abb2e4687bAlan Viverette            mHasCheckedVectorDrawableSetup = true;
691eb648620b291eabbb5523f649baef5abb2e4687bAlan Viverette        } else {
692eb648620b291eabbb5523f649baef5abb2e4687bAlan Viverette            throw new IllegalStateException("This app has been built with an incorrect "
693eb648620b291eabbb5523f649baef5abb2e4687bAlan Viverette                    + "configuration. Please configure your build for VectorDrawableCompat.");
694eb648620b291eabbb5523f649baef5abb2e4687bAlan Viverette        }
695eb648620b291eabbb5523f649baef5abb2e4687bAlan Viverette    }
696eb648620b291eabbb5523f649baef5abb2e4687bAlan Viverette
697ba13a44cd6db80ec983b1ed678b7dbeab04056a1Chris Banes    private static boolean isVectorDrawable(@NonNull Drawable d) {
698ba13a44cd6db80ec983b1ed678b7dbeab04056a1Chris Banes        return d instanceof VectorDrawableCompat
699ba13a44cd6db80ec983b1ed678b7dbeab04056a1Chris Banes                || PLATFORM_VD_CLAZZ.equals(d.getClass().getName());
700ba13a44cd6db80ec983b1ed678b7dbeab04056a1Chris Banes    }
701ba13a44cd6db80ec983b1ed678b7dbeab04056a1Chris Banes
7029f63864c84e3d48841d553521468c7a27189c4f0Chris Banes    private static class VdcInflateDelegate implements InflateDelegate {
7039f63864c84e3d48841d553521468c7a27189c4f0Chris Banes        @Override
704bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes        public Drawable createFromXmlInner(@NonNull Context context, @NonNull XmlPullParser parser,
7059f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                @NonNull AttributeSet attrs, @Nullable Resources.Theme theme) {
7069f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            try {
707bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes                return VectorDrawableCompat
708bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes                        .createFromXmlInner(context.getResources(), parser, attrs, theme);
7099f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            } catch (Exception e) {
7109f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                Log.e("VdcInflateDelegate", "Exception while inflating <vector>", e);
7119f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                return null;
7129f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            }
7139f63864c84e3d48841d553521468c7a27189c4f0Chris Banes        }
7149f63864c84e3d48841d553521468c7a27189c4f0Chris Banes    }
715bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes
716bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes    private static class AvdcInflateDelegate implements InflateDelegate {
717bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes        @Override
718bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes        public Drawable createFromXmlInner(@NonNull Context context, @NonNull XmlPullParser parser,
719bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes                @NonNull AttributeSet attrs, @Nullable Resources.Theme theme) {
720bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes            try {
721bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes                return AnimatedVectorDrawableCompat
722bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes                        .createFromXmlInner(context, context.getResources(), parser, attrs, theme);
723bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes            } catch (Exception e) {
724bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes                Log.e("AvdcInflateDelegate", "Exception while inflating <animated-vector>", e);
725bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes                return null;
726bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes            }
727bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes        }
728bd3494de1d97206f366f52641a14b21eb06a7022Chris Banes    }
729469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes}
730