115aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes/* 215aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes * Copyright (C) 2016 The Android Open Source Project 315aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes * 415aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes * Licensed under the Apache License, Version 2.0 (the "License"); 515aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes * you may not use this file except in compliance with the License. 615aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes * You may obtain a copy of the License at 715aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes * 815aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes * http://www.apache.org/licenses/LICENSE-2.0 915aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes * 1015aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes * Unless required by applicable law or agreed to in writing, software 1115aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes * distributed under the License is distributed on an "AS IS" BASIS, 1215aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1315aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes * See the License for the specific language governing permissions and 1415aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes * limitations under the License. 1515aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes */ 1615aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes 1715aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banespackage android.support.v7.content.res; 1815aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes 1915aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banesimport android.content.Context; 2015aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banesimport android.content.res.ColorStateList; 2115aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banesimport android.content.res.Configuration; 2215aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banesimport android.content.res.Resources; 234c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banesimport android.graphics.drawable.Drawable; 2415aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banesimport android.os.Build; 2515aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banesimport android.support.annotation.ColorRes; 264c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banesimport android.support.annotation.DrawableRes; 2715aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banesimport android.support.annotation.NonNull; 2815aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banesimport android.support.annotation.Nullable; 2915aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banesimport android.support.v4.content.ContextCompat; 304c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banesimport android.support.v7.widget.AppCompatDrawableManager; 3115aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banesimport android.util.Log; 3215aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banesimport android.util.SparseArray; 3315aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banesimport android.util.TypedValue; 3415aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes 354c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banesimport org.xmlpull.v1.XmlPullParser; 364c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes 3715aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banesimport java.util.WeakHashMap; 3815aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes 3915aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes/** 4015aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes * Class for accessing an application's resources through AppCompat, and thus any backward 4115aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes * compatible functionality. 4215aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes */ 4315aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banespublic final class AppCompatResources { 4415aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes 4515aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes private static final String LOG_TAG = "AppCompatResources"; 4615aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes private static final ThreadLocal<TypedValue> TL_TYPED_VALUE = new ThreadLocal<>(); 4715aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes 4815aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes private static final WeakHashMap<Context, SparseArray<ColorStateListCacheEntry>> 4915aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes sColorStateCaches = new WeakHashMap<>(0); 5015aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes 5115aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes private static final Object sColorStateCacheLock = new Object(); 5215aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes 5315aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes private AppCompatResources() {} 5415aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes 5515aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes /** 5615aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes * Returns the {@link ColorStateList} from the given resource. The resource can include 5715aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes * themeable attributes, regardless of API level. 5815aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes * 594cf1d92509224fab3ca69b419a005c536ab75c5cChris Banes * @param context context to inflate against 6015aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes * @param resId the resource identifier of the ColorStateList to retrieve 6115aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes */ 6215aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes public static ColorStateList getColorStateList(@NonNull Context context, @ColorRes int resId) { 6315aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes if (Build.VERSION.SDK_INT >= 23) { 6415aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes // On M+ we can use the framework 6515aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes return context.getColorStateList(resId); 6615aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes } 6715aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes 6815aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes // Before that, we'll try handle it ourselves 6915aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes ColorStateList csl = getCachedColorStateList(context, resId); 7015aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes if (csl != null) { 7115aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes return csl; 7215aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes } 7315aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes // Cache miss, so try and inflate it ourselves 7415aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes csl = inflateColorStateList(context, resId); 7515aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes if (csl != null) { 7615aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes // If we inflated it, add it to the cache and return 7715aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes addColorStateListToCache(context, resId, csl); 7815aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes return csl; 7915aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes } 8015aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes 8115aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes // If we reach here then we couldn't inflate it, so let the framework handle it 8215aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes return ContextCompat.getColorStateList(context, resId); 8315aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes } 8415aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes 8515aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes /** 864c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes * Return a drawable object associated with a particular resource ID. 874c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes * 884c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes * <p>This method supports inflation of {@code <vector>} and {@code <animated-vector>} 894c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes * resources on devices where platform support is not available.</p> 904c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes * 914cf1d92509224fab3ca69b419a005c536ab75c5cChris Banes * @param context context to inflate against 924cf1d92509224fab3ca69b419a005c536ab75c5cChris Banes * @param resId The desired resource identifier, as generated by the aapt 934cf1d92509224fab3ca69b419a005c536ab75c5cChris Banes * tool. This integer encodes the package, type, and resource 944cf1d92509224fab3ca69b419a005c536ab75c5cChris Banes * entry. The value 0 is an invalid identifier. 954c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes * @return Drawable An object that can be used to draw this resource. 964c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes * @see ContextCompat#getDrawable(Context, int) 974c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes */ 984c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes @Nullable 994c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes public static Drawable getDrawable(@NonNull Context context, @DrawableRes int resId) { 1004c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes return AppCompatDrawableManager.get().getDrawable(context, resId); 1014c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes } 1024c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes 1034c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes /** 10415aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes * Inflates a {@link ColorStateList} from resources, honouring theme attributes. 10515aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes */ 10615aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes @Nullable 10715aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes private static ColorStateList inflateColorStateList(Context context, int resId) { 10815aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes if (isColorInt(context, resId)) { 10915aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes // The resource is a color int, we can't handle it so return null 11015aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes return null; 11115aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes } 11215aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes 11315aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes final Resources r = context.getResources(); 11415aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes final XmlPullParser xml = r.getXml(resId); 11515aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes try { 11615aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes return AppCompatColorStateListInflater.createFromXml(r, xml, context.getTheme()); 11715aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes } catch (Exception e) { 11815aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes Log.e(LOG_TAG, "Failed to inflate ColorStateList, leaving it to the framework", e); 11915aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes } 12015aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes return null; 12115aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes } 12215aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes 12315aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes @Nullable 12415aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes private static ColorStateList getCachedColorStateList(@NonNull Context context, 12515aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes @ColorRes int resId) { 12615aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes synchronized (sColorStateCacheLock) { 12715aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes final SparseArray<ColorStateListCacheEntry> entries = sColorStateCaches.get(context); 12815aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes if (entries != null && entries.size() > 0) { 12915aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes final ColorStateListCacheEntry entry = entries.get(resId); 13015aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes if (entry != null) { 13115aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes if (entry.configuration.equals(context.getResources().getConfiguration())) { 13215aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes // If the current configuration matches the entry's, we can use it 13315aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes return entry.value; 13415aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes } else { 13515aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes // Otherwise we'll remove the entry 13615aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes entries.remove(resId); 13715aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes } 13815aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes } 13915aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes } 14015aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes } 14115aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes return null; 14215aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes } 14315aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes 14415aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes private static void addColorStateListToCache(@NonNull Context context, @ColorRes int resId, 14515aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes @NonNull ColorStateList value) { 14615aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes synchronized (sColorStateCacheLock) { 14715aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes SparseArray<ColorStateListCacheEntry> entries = sColorStateCaches.get(context); 14815aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes if (entries == null) { 14915aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes entries = new SparseArray<>(); 15015aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes sColorStateCaches.put(context, entries); 15115aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes } 15215aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes entries.append(resId, new ColorStateListCacheEntry(value, 15315aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes context.getResources().getConfiguration())); 15415aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes } 15515aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes } 15615aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes 15715aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes private static boolean isColorInt(@NonNull Context context, @ColorRes int resId) { 15815aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes final Resources r = context.getResources(); 15915aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes 16015aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes final TypedValue value = getTypedValue(); 16115aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes r.getValue(resId, value, true); 16215aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes 16315aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes return value.type >= TypedValue.TYPE_FIRST_COLOR_INT 16415aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes && value.type <= TypedValue.TYPE_LAST_COLOR_INT; 16515aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes } 16615aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes 16715aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes @NonNull 16815aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes private static TypedValue getTypedValue() { 16915aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes TypedValue tv = TL_TYPED_VALUE.get(); 17015aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes if (tv == null) { 17115aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes tv = new TypedValue(); 17215aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes TL_TYPED_VALUE.set(tv); 17315aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes } 17415aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes return tv; 17515aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes } 17615aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes 17715aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes private static class ColorStateListCacheEntry { 17815aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes final ColorStateList value; 17915aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes final Configuration configuration; 18015aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes 18115aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes ColorStateListCacheEntry(@NonNull ColorStateList value, 18215aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes @NonNull Configuration configuration) { 18315aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes this.value = value; 18415aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes this.configuration = configuration; 18515aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes } 18615aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes } 18715aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes 18815aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes} 189