| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package androidx.appcompat.content.res; |
| |
| import android.content.Context; |
| import android.content.res.ColorStateList; |
| import android.content.res.Configuration; |
| import android.content.res.Resources; |
| import android.graphics.drawable.Drawable; |
| import android.os.Build; |
| import android.util.Log; |
| import android.util.SparseArray; |
| import android.util.TypedValue; |
| |
| import androidx.annotation.ColorRes; |
| import androidx.annotation.DrawableRes; |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.appcompat.widget.AppCompatDrawableManager; |
| import androidx.core.content.ContextCompat; |
| |
| import org.xmlpull.v1.XmlPullParser; |
| |
| import java.util.WeakHashMap; |
| |
| /** |
| * Class for accessing an application's resources through AppCompat, and thus any backward |
| * compatible functionality. |
| */ |
| public final class AppCompatResources { |
| |
| private static final String LOG_TAG = "AppCompatResources"; |
| private static final ThreadLocal<TypedValue> TL_TYPED_VALUE = new ThreadLocal<>(); |
| |
| private static final WeakHashMap<Context, SparseArray<ColorStateListCacheEntry>> |
| sColorStateCaches = new WeakHashMap<>(0); |
| |
| private static final Object sColorStateCacheLock = new Object(); |
| |
| private AppCompatResources() {} |
| |
| /** |
| * Returns the {@link ColorStateList} from the given resource. The resource can include |
| * themeable attributes, regardless of API level. |
| * |
| * @param context context to inflate against |
| * @param resId the resource identifier of the ColorStateList to retrieve |
| */ |
| public static ColorStateList getColorStateList(@NonNull Context context, @ColorRes int resId) { |
| if (Build.VERSION.SDK_INT >= 23) { |
| // On M+ we can use the framework |
| return context.getColorStateList(resId); |
| } |
| |
| // Before that, we'll try handle it ourselves |
| ColorStateList csl = getCachedColorStateList(context, resId); |
| if (csl != null) { |
| return csl; |
| } |
| // Cache miss, so try and inflate it ourselves |
| csl = inflateColorStateList(context, resId); |
| if (csl != null) { |
| // If we inflated it, add it to the cache and return |
| addColorStateListToCache(context, resId, csl); |
| return csl; |
| } |
| |
| // If we reach here then we couldn't inflate it, so let the framework handle it |
| return ContextCompat.getColorStateList(context, resId); |
| } |
| |
| /** |
| * Return a drawable object associated with a particular resource ID. |
| * |
| * <p>This method supports inflation of {@code <vector>} and {@code <animated-vector>} |
| * resources on devices where platform support is not available.</p> |
| * |
| * @param context context to inflate against |
| * @param resId The desired resource identifier, as generated by the aapt |
| * tool. This integer encodes the package, type, and resource |
| * entry. The value 0 is an invalid identifier. |
| * @return Drawable An object that can be used to draw this resource. |
| * @see ContextCompat#getDrawable(Context, int) |
| */ |
| @Nullable |
| public static Drawable getDrawable(@NonNull Context context, @DrawableRes int resId) { |
| return AppCompatDrawableManager.get().getDrawable(context, resId); |
| } |
| |
| /** |
| * Inflates a {@link ColorStateList} from resources, honouring theme attributes. |
| */ |
| @Nullable |
| private static ColorStateList inflateColorStateList(Context context, int resId) { |
| if (isColorInt(context, resId)) { |
| // The resource is a color int, we can't handle it so return null |
| return null; |
| } |
| |
| final Resources r = context.getResources(); |
| final XmlPullParser xml = r.getXml(resId); |
| try { |
| return AppCompatColorStateListInflater.createFromXml(r, xml, context.getTheme()); |
| } catch (Exception e) { |
| Log.e(LOG_TAG, "Failed to inflate ColorStateList, leaving it to the framework", e); |
| } |
| return null; |
| } |
| |
| @Nullable |
| private static ColorStateList getCachedColorStateList(@NonNull Context context, |
| @ColorRes int resId) { |
| synchronized (sColorStateCacheLock) { |
| final SparseArray<ColorStateListCacheEntry> entries = sColorStateCaches.get(context); |
| if (entries != null && entries.size() > 0) { |
| final ColorStateListCacheEntry entry = entries.get(resId); |
| if (entry != null) { |
| if (entry.configuration.equals(context.getResources().getConfiguration())) { |
| // If the current configuration matches the entry's, we can use it |
| return entry.value; |
| } else { |
| // Otherwise we'll remove the entry |
| entries.remove(resId); |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| private static void addColorStateListToCache(@NonNull Context context, @ColorRes int resId, |
| @NonNull ColorStateList value) { |
| synchronized (sColorStateCacheLock) { |
| SparseArray<ColorStateListCacheEntry> entries = sColorStateCaches.get(context); |
| if (entries == null) { |
| entries = new SparseArray<>(); |
| sColorStateCaches.put(context, entries); |
| } |
| entries.append(resId, new ColorStateListCacheEntry(value, |
| context.getResources().getConfiguration())); |
| } |
| } |
| |
| private static boolean isColorInt(@NonNull Context context, @ColorRes int resId) { |
| final Resources r = context.getResources(); |
| |
| final TypedValue value = getTypedValue(); |
| r.getValue(resId, value, true); |
| |
| return value.type >= TypedValue.TYPE_FIRST_COLOR_INT |
| && value.type <= TypedValue.TYPE_LAST_COLOR_INT; |
| } |
| |
| @NonNull |
| private static TypedValue getTypedValue() { |
| TypedValue tv = TL_TYPED_VALUE.get(); |
| if (tv == null) { |
| tv = new TypedValue(); |
| TL_TYPED_VALUE.set(tv); |
| } |
| return tv; |
| } |
| |
| private static class ColorStateListCacheEntry { |
| final ColorStateList value; |
| final Configuration configuration; |
| |
| ColorStateListCacheEntry(@NonNull ColorStateList value, |
| @NonNull Configuration configuration) { |
| this.value = value; |
| this.configuration = configuration; |
| } |
| } |
| |
| } |