[go: nahoru, domu]

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;
         }
     }