Merge "Add CSL value support to ColorStateListInflaterCompat" into androidx-master-dev
diff --git a/appcompat/appcompat-resources/src/main/java/androidx/appcompat/content/res/AppCompatResources.java b/appcompat/appcompat-resources/src/main/java/androidx/appcompat/content/res/AppCompatResources.java
index 025d2c8..3ee413a 100644
--- a/appcompat/appcompat-resources/src/main/java/androidx/appcompat/content/res/AppCompatResources.java
+++ b/appcompat/appcompat-resources/src/main/java/androidx/appcompat/content/res/AppCompatResources.java
@@ -19,13 +19,7 @@
import android.annotation.SuppressLint;
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;
@@ -33,11 +27,6 @@
import androidx.annotation.Nullable;
import androidx.appcompat.widget.ResourceManagerInternal;
import androidx.core.content.ContextCompat;
-import androidx.core.content.res.ColorStateListInflaterCompat;
-
-import org.xmlpull.v1.XmlPullParser;
-
-import java.util.WeakHashMap;
/**
* Class for accessing an application's resources through AppCompat, and thus any backward
@@ -46,14 +35,6 @@
@SuppressLint("RestrictedAPI") // Temporary until we have correct restriction scopes for 1.0
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() {}
/**
@@ -64,25 +45,6 @@
* @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);
}
@@ -104,89 +66,4 @@
return ResourceManagerInternal.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 ColorStateListInflaterCompat.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;
- }
- }
-
}
diff --git a/core/core/src/androidTest/java/androidx/core/content/res/ColorStateListInflaterCompatTest.java b/core/core/src/androidTest/java/androidx/core/content/res/ColorStateListInflaterCompatTest.java
index 23d80c9..1fb29fa 100644
--- a/core/core/src/androidTest/java/androidx/core/content/res/ColorStateListInflaterCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/content/res/ColorStateListInflaterCompatTest.java
@@ -26,6 +26,9 @@
import android.content.res.TypedArray;
import android.graphics.Color;
+import androidx.annotation.AttrRes;
+import androidx.annotation.ColorInt;
+import androidx.appcompat.view.ContextThemeWrapper;
import androidx.core.test.R;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -47,19 +50,16 @@
@Before
public void setup() {
- mContext = ApplicationProvider.getApplicationContext();
+ mContext = new ContextThemeWrapper(ApplicationProvider.getApplicationContext(),
+ R.style.ThemeOverlay_Core_ColorStateListInflaterCompat);
mResources = mContext.getResources();
}
@Test
public void testGetColorStateListWithThemedAttributes() throws Exception {
- TypedArray a = TypedArrayUtils.obtainAttributes(mResources, mContext.getTheme(), null,
- new int[]{android.R.attr.colorForeground});
- final int colorForeground = a.getColor(0, 0);
- a.recycle();
+ final int colorForeground = getColorFromTheme(android.R.attr.colorForeground);
- @SuppressLint("ResourceType")
- final ColorStateList result =
+ @SuppressLint("ResourceType") final ColorStateList result =
ColorStateListInflaterCompat.createFromXml(mResources,
mResources.getXml(R.color.color_state_list_themed_attrs),
mContext.getTheme());
@@ -71,10 +71,42 @@
// Disabled color should be colorForeground with 50% of its alpha
final int expectedDisabled = Color.argb(128, Color.red(colorForeground),
Color.green(colorForeground), Color.blue(colorForeground));
- assertEquals(expectedDisabled, result.getColorForState(
- new int[]{-android.R.attr.state_enabled}, 0));
+ assertEquals(expectedDisabled, getColorForDisabledState(result));
// Default color should equal colorForeground
assertEquals(colorForeground, result.getDefaultColor());
}
+
+ @Test
+ public void testGetColorStateListWithColorStateListAsValue() throws Exception {
+ final int textColorPrimary = getColorFromTheme(android.R.attr.textColorPrimary);
+
+ @SuppressLint("ResourceType") final ColorStateList result =
+ ColorStateListInflaterCompat.createFromXml(mResources,
+ mResources.getXml(R.color.color_state_list_secondary_text),
+ mContext.getTheme());
+
+ assertNotNull(result);
+
+ // Now check the state colors
+
+ // Default color should be textColorPrimary with 54% of its alpha
+ final int expectedTextColorSecondary = Color.argb(138, Color.red(textColorPrimary),
+ Color.green(textColorPrimary), Color.blue(textColorPrimary));
+ assertEquals(expectedTextColorSecondary, result.getDefaultColor());
+ }
+
+ @ColorInt
+ private int getColorFromTheme(@AttrRes int attrResId) {
+ TypedArray a = TypedArrayUtils.obtainAttributes(mResources, mContext.getTheme(), null,
+ new int[]{attrResId});
+ final int colorForeground = a.getColor(0, 0);
+ a.recycle();
+ return colorForeground;
+ }
+
+ private int getColorForDisabledState(ColorStateList colorStateList) {
+ return colorStateList.getColorForState(
+ new int[]{-android.R.attr.state_enabled}, 0);
+ }
}
diff --git a/core/core/src/androidTest/res/color/color_state_list_secondary_text.xml b/core/core/src/androidTest/res/color/color_state_list_secondary_text.xml
new file mode 100644
index 0000000..8e4f135
--- /dev/null
+++ b/core/core/src/androidTest/res/color/color_state_list_secondary_text.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item
+ android:color="?android:attr/textColorPrimary"
+ app:alpha="0.54" />
+</selector>
diff --git a/core/core/src/androidTest/res/values/styles.xml b/core/core/src/androidTest/res/values/styles.xml
index b74bacc..46580a2 100644
--- a/core/core/src/androidTest/res/values/styles.xml
+++ b/core/core/src/androidTest/res/values/styles.xml
@@ -34,4 +34,8 @@
<item name="theme_color_focused">@color/theme_color_lilac_focused</item>
<item name="theme_color_pressed">@color/theme_color_lilac_pressed</item>
</style>
+
+ <style name="ThemeOverlay.Core.ColorStateListInflaterCompat" parent="">
+ <item name="android:textColorPrimary">@color/text_color</item>
+ </style>
</resources>
\ No newline at end of file
diff --git a/core/core/src/main/java/androidx/core/content/ContextCompat.java b/core/core/src/main/java/androidx/core/content/ContextCompat.java
index 6b0af66..5068672 100644
--- a/core/core/src/main/java/androidx/core/content/ContextCompat.java
+++ b/core/core/src/main/java/androidx/core/content/ContextCompat.java
@@ -137,6 +137,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityOptionsCompat;
+import androidx.core.content.res.ResourcesCompat;
import androidx.core.os.EnvironmentCompat;
import java.io.File;
@@ -486,15 +487,9 @@
* @throws android.content.res.Resources.NotFoundException if the given ID
* does not exist.
*/
- @SuppressWarnings("deprecation")
@Nullable
- public static ColorStateList getColorStateList(@NonNull Context context,
- @ColorRes int id) {
- if (Build.VERSION.SDK_INT >= 23) {
- return context.getColorStateList(id);
- } else {
- return context.getResources().getColorStateList(id);
- }
+ public static ColorStateList getColorStateList(@NonNull Context context, @ColorRes int id) {
+ return ResourcesCompat.getColorStateList(context.getResources(), id, context.getTheme());
}
/**
diff --git a/core/core/src/main/java/androidx/core/content/res/ColorStateListInflaterCompat.java b/core/core/src/main/java/androidx/core/content/res/ColorStateListInflaterCompat.java
index 0f90a91..3b1f6d1 100644
--- a/core/core/src/main/java/androidx/core/content/res/ColorStateListInflaterCompat.java
+++ b/core/core/src/main/java/androidx/core/content/res/ColorStateListInflaterCompat.java
@@ -25,9 +25,11 @@
import android.util.AttributeSet;
import android.util.Log;
import android.util.StateSet;
+import android.util.TypedValue;
import android.util.Xml;
import androidx.annotation.ColorInt;
+import androidx.annotation.ColorRes;
import androidx.annotation.FloatRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -46,6 +48,8 @@
@RestrictTo(LIBRARY_GROUP_PREFIX)
public final class ColorStateListInflaterCompat {
+ private static final ThreadLocal<TypedValue> sTempTypedValue = new ThreadLocal<>();
+
private ColorStateListInflaterCompat() {
}
@@ -141,8 +145,18 @@
}
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ColorStateListItem);
- final int baseColor = a.getColor(R.styleable.ColorStateListItem_android_color,
- Color.MAGENTA);
+ int resourceId = a.getResourceId(R.styleable.ColorStateListItem_android_color, -1);
+ int baseColor;
+ if (resourceId != -1 && !isColorInt(r, resourceId)) {
+ try {
+ baseColor = createFromXml(r, r.getXml(resourceId), theme).getDefaultColor();
+ } catch (Exception e) {
+ baseColor = a.getColor(R.styleable.ColorStateListItem_android_color,
+ Color.MAGENTA);
+ }
+ } else {
+ baseColor = a.getColor(R.styleable.ColorStateListItem_android_color, Color.MAGENTA);
+ }
float alphaMod = 1.0f;
if (a.hasValue(R.styleable.ColorStateListItem_android_alpha)) {
@@ -186,6 +200,24 @@
return new ColorStateList(stateSpecs, colors);
}
+ private static boolean isColorInt(@NonNull Resources r, @ColorRes int resId) {
+ 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 = sTempTypedValue.get();
+ if (tv == null) {
+ tv = new TypedValue();
+ sTempTypedValue.set(tv);
+ }
+ return tv;
+ }
+
private static TypedArray obtainAttributes(Resources res, Resources.Theme theme,
AttributeSet set, int[] attrs) {
return theme == null ? res.obtainAttributes(set, attrs)
diff --git a/core/core/src/main/java/androidx/core/content/res/ResourcesCompat.java b/core/core/src/main/java/androidx/core/content/res/ResourcesCompat.java
index ffc60ae7..a3b8cb9 100644
--- a/core/core/src/main/java/androidx/core/content/res/ResourcesCompat.java
+++ b/core/core/src/main/java/androidx/core/content/res/ResourcesCompat.java
@@ -22,15 +22,18 @@
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.content.res.Resources.Theme;
import android.content.res.XmlResourceParser;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
+import android.util.SparseArray;
import android.util.TypedValue;
import androidx.annotation.AnyRes;
@@ -49,17 +52,25 @@
import androidx.core.provider.FontsContractCompat.FontRequestCallback.FontRequestFailReason;
import androidx.core.util.Preconditions;
+import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.util.WeakHashMap;
/**
* Helper for accessing features in {@link android.content.res.Resources}.
*/
public final class ResourcesCompat {
private static final String TAG = "ResourcesCompat";
+ private static final ThreadLocal<TypedValue> sTempTypedValue = new ThreadLocal<>();
+
+ private static final WeakHashMap<Resources, SparseArray<ColorStateListCacheEntry>>
+ sColorStateCaches = new WeakHashMap<>(0);
+
+ private static final Object sColorStateCacheLock = new Object();
/**
* The {@code null} resource ID. This denotes an invalid resource ID that is returned by the
@@ -183,10 +194,108 @@
@SuppressWarnings("deprecation")
public static ColorStateList getColorStateList(@NonNull Resources res, @ColorRes int id,
@Nullable Theme theme) throws NotFoundException {
- if (SDK_INT >= 23) {
+ if (Build.VERSION.SDK_INT >= 23) {
+ // On M+ we can use the framework
return res.getColorStateList(id, theme);
- } else {
- return res.getColorStateList(id);
+ }
+
+ // Before that, we'll try handle it ourselves
+ ColorStateList csl = getCachedColorStateList(res, id);
+ if (csl != null) {
+ return csl;
+ }
+ // Cache miss, so try and inflate it ourselves
+ csl = inflateColorStateList(res, id, theme);
+ if (csl != null) {
+ // If we inflated it, add it to the cache and return
+ addColorStateListToCache(res, id, csl);
+ return csl;
+ }
+
+ // If we reach here then we couldn't inflate it, so let the framework handle it
+ return res.getColorStateList(id);
+ }
+
+ /**
+ * Inflates a {@link ColorStateList} from resources, honouring theme attributes.
+ */
+ @Nullable
+ private static ColorStateList inflateColorStateList(Resources resources, int resId,
+ @Nullable Theme theme) {
+ if (isColorInt(resources, resId)) {
+ // The resource is a color int, we can't handle it so return null
+ return null;
+ }
+
+ final XmlPullParser xml = resources.getXml(resId);
+ try {
+ return ColorStateListInflaterCompat.createFromXml(resources, xml, theme);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to inflate ColorStateList, leaving it to the framework", e);
+ }
+ return null;
+ }
+
+ @Nullable
+ private static ColorStateList getCachedColorStateList(@NonNull Resources resources,
+ @ColorRes int resId) {
+ synchronized (sColorStateCacheLock) {
+ final SparseArray<ColorStateListCacheEntry> entries = sColorStateCaches.get(resources);
+ if (entries != null && entries.size() > 0) {
+ final ColorStateListCacheEntry entry = entries.get(resId);
+ if (entry != null) {
+ if (entry.mConfiguration.equals(resources.getConfiguration())) {
+ // If the current configuration matches the entry's, we can use it
+ return entry.mValue;
+ } else {
+ // Otherwise we'll remove the entry
+ entries.remove(resId);
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private static void addColorStateListToCache(@NonNull Resources resources, @ColorRes int resId,
+ @NonNull ColorStateList value) {
+ synchronized (sColorStateCacheLock) {
+ SparseArray<ColorStateListCacheEntry> entries = sColorStateCaches.get(resources);
+ if (entries == null) {
+ entries = new SparseArray<>();
+ sColorStateCaches.put(resources, entries);
+ }
+ entries.append(resId, new ColorStateListCacheEntry(value,
+ resources.getConfiguration()));
+ }
+ }
+
+ private static boolean isColorInt(@NonNull Resources resources, @ColorRes int resId) {
+ final TypedValue value = getTypedValue();
+ resources.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 = sTempTypedValue.get();
+ if (tv == null) {
+ tv = new TypedValue();
+ sTempTypedValue.set(tv);
+ }
+ return tv;
+ }
+
+ private static class ColorStateListCacheEntry {
+ final ColorStateList mValue;
+ final Configuration mConfiguration;
+
+ ColorStateListCacheEntry(@NonNull ColorStateList value,
+ @NonNull Configuration configuration) {
+ mValue = value;
+ mConfiguration = configuration;
}
}