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