Add support for color state lists and theme attributes in color filters
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java b/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
index 8441d44..18ff5c8 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
@@ -3,10 +3,10 @@
 import android.animation.Animator;
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.ColorFilter;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
@@ -25,6 +25,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RawRes;
 import androidx.annotation.RequiresApi;
+import androidx.appcompat.content.res.AppCompatResources;
 import androidx.appcompat.widget.AppCompatImageView;
 import androidx.core.view.ViewCompat;
 
@@ -194,8 +195,9 @@
     enableMergePathsForKitKatAndAbove(ta.getBoolean(
         R.styleable.LottieAnimationView_lottie_enableMergePathsForKitKatAndAbove, false));
     if (ta.hasValue(R.styleable.LottieAnimationView_lottie_colorFilter)) {
-      SimpleColorFilter filter = new SimpleColorFilter(
-          ta.getColor(R.styleable.LottieAnimationView_lottie_colorFilter, Color.TRANSPARENT));
+      int colorRes = ta.getResourceId(R.styleable.LottieAnimationView_lottie_colorFilter, -1);
+      ColorStateList csl = AppCompatResources.getColorStateList(getContext(), colorRes);
+      SimpleColorFilter filter = new SimpleColorFilter(csl.getDefaultColor());
       KeyPath keyPath = new KeyPath("**");
       LottieValueCallback<ColorFilter> callback = new LottieValueCallback<ColorFilter>(filter);
       addValueCallback(keyPath, LottieProperty.COLOR_FILTER, callback);
diff --git a/sample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.kt b/sample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.kt
index 6db0e7a..9394939 100644
--- a/sample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.kt
+++ b/sample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.kt
@@ -7,6 +7,7 @@
 import android.graphics.*
 import android.util.DisplayMetrics
 import android.util.Log
+import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import android.widget.FrameLayout
@@ -16,11 +17,11 @@
 import androidx.test.ext.junit.rules.ActivityScenarioRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
-import androidx.test.rule.ActivityTestRule
 import androidx.test.rule.GrantPermissionRule
 import com.airbnb.lottie.*
 import com.airbnb.lottie.model.KeyPath
 import com.airbnb.lottie.model.LottieCompositionCache
+import com.airbnb.lottie.samples.databinding.TestColorFilterBinding
 import com.airbnb.lottie.samples.testing.NoCacheLottieAnimationView
 import com.airbnb.lottie.samples.views.FilmStripView
 import com.airbnb.lottie.value.*
@@ -37,6 +38,7 @@
 import org.junit.runner.RunWith
 import java.io.File
 import java.io.FileInputStream
+import java.util.concurrent.Executors
 import java.util.concurrent.TimeUnit
 import java.util.zip.ZipInputStream
 
@@ -51,7 +53,7 @@
     private val application get() = ApplicationProvider.getApplicationContext<Context>()
 
     @get:Rule
-    val permissionRule = GrantPermissionRule.grant(
+    val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(
             Manifest.permission.WRITE_EXTERNAL_STORAGE,
             Manifest.permission.READ_EXTERNAL_STORAGE
     )
@@ -82,6 +84,7 @@
         }
     }
 
+    @Suppress("BlockingMethodInNonBlockingContext")
     @Before
     fun setup() {
         L.DBG = false
@@ -97,15 +100,16 @@
     @Test
     fun testAll() = runBlocking {
         withTimeout(TimeUnit.MINUTES.toMillis(45)) {
-            snapshotFailure()
+            testColorStateListColorFilter()
+            testFailure()
             snapshotFrameBoundaries()
             snapshotScaleTypes()
             testDynamicProperties()
             testMarkers()
-            snapshotAssets()
+            testAssets()
             testText()
             testPartialFrameProgress()
-            snapshotProdAnimations()
+            testProdAnimations()
             testNightMode()
             testApplyOpacityToLayer()
             testOutlineMasksAndMattes()
@@ -113,7 +117,7 @@
         }
     }
 
-    private suspend fun snapshotProdAnimations() = coroutineScope {
+    private suspend fun testProdAnimations() = coroutineScope {
         val s3Client = AmazonS3Client(BasicAWSCredentials(BuildConfig.S3AccessKey, BuildConfig.S3SecretKey))
         val allObjects = s3Client.fetchAllObjects("lottie-prod-animations")
 
@@ -122,7 +126,7 @@
         repeat(4) { snapshotCompositions(compositionsChannel) }
     }
 
-    private fun CoroutineScope.downloadAnimations(animations: List<S3ObjectSummary>) = produce<File>(
+    private fun CoroutineScope.downloadAnimations(animations: List<S3ObjectSummary>) = produce(
             context = Dispatchers.IO,
             capacity = 10
     ) {
@@ -134,7 +138,8 @@
         }
     }
 
-    private fun CoroutineScope.parseCompositions(files: ReceiveChannel<File>) = produce<Pair<String, LottieComposition>>(
+    @Suppress("BlockingMethodInNonBlockingContext")
+    private fun CoroutineScope.parseCompositions(files: ReceiveChannel<File>) = produce(
             context = Dispatchers.Default,
             capacity = 1
     ) {
@@ -183,8 +188,7 @@
         bitmapPool.release(bitmap)
     }
 
-    @ObsoleteCoroutinesApi
-    private suspend fun snapshotAssets() = coroutineScope {
+    private suspend fun testAssets() = coroutineScope {
         val assetsChannel = listAssets()
         val compositionsChannel = parseCompositionsFromAssets(assetsChannel)
         repeat(4) { snapshotCompositions(compositionsChannel) }
@@ -203,9 +207,8 @@
         return assets
     }
 
-    @ObsoleteCoroutinesApi
-    private fun CoroutineScope.parseCompositionsFromAssets(assets: List<String>) = produce<Pair<String, LottieComposition>>(
-            context = newSingleThreadContext("Parsing"),
+    private fun CoroutineScope.parseCompositionsFromAssets(assets: List<String>) = produce(
+            context = Executors.newSingleThreadExecutor().asCoroutineDispatcher(),
             capacity = 10
     ) {
         for (asset in assets) {
@@ -216,7 +219,7 @@
         }
     }
 
-    private suspend fun snapshotFailure() {
+    private suspend fun testFailure() {
         val animationView = animationViewPool.acquire()
         val semaphore = SuspendingSemaphore(0)
         animationView.setFailureListener { semaphore.release() }
@@ -662,13 +665,13 @@
                 "Color Filter",
                 KeyPath("**"),
                 LottieProperty.COLOR_FILTER,
-                LottieValueCallback<ColorFilter>(SimpleColorFilter(Color.GREEN)))
+                LottieValueCallback(SimpleColorFilter(Color.GREEN)))
 
         testDynamicProperty(
                 "Null Color Filter",
                 KeyPath("**"),
                 LottieProperty.COLOR_FILTER,
-                LottieValueCallback<ColorFilter>(null))
+                LottieValueCallback(null))
 
         testDynamicProperty(
                 "Opacity interpolation (0)",
@@ -693,7 +696,7 @@
 
         withDrawable("Tests/DynamicGradient.json", "Gradient Colors", "Linear Gradient Fill") { drawable ->
             val value = object : LottieValueCallback<Array<Int>>() {
-                override fun getValue(frameInfo: LottieFrameInfo<Array<Int>>?): Array<Int>? {
+                override fun getValue(frameInfo: LottieFrameInfo<Array<Int>>?): Array<Int> {
                     return arrayOf(Color.YELLOW, Color.GREEN)
                 }
             }
@@ -702,7 +705,7 @@
 
         withDrawable("Tests/DynamicGradient.json", "Gradient Colors", "Radial Gradient Fill") { drawable ->
             val value = object : LottieValueCallback<Array<Int>>() {
-                override fun getValue(frameInfo: LottieFrameInfo<Array<Int>>?): Array<Int>? {
+                override fun getValue(frameInfo: LottieFrameInfo<Array<Int>>?): Array<Int> {
                     return arrayOf(Color.YELLOW, Color.GREEN)
                 }
             }
@@ -711,7 +714,7 @@
 
         withDrawable("Tests/DynamicGradient.json", "Gradient Colors", "Linear Gradient Stroke") { drawable ->
             val value = object : LottieValueCallback<Array<Int>>() {
-                override fun getValue(frameInfo: LottieFrameInfo<Array<Int>>?): Array<Int>? {
+                override fun getValue(frameInfo: LottieFrameInfo<Array<Int>>?): Array<Int> {
                     return arrayOf(Color.YELLOW, Color.GREEN)
                 }
             }
@@ -720,7 +723,7 @@
 
         withDrawable("Tests/DynamicGradient.json", "Gradient Colors", "Radial Gradient Stroke") { drawable ->
             val value = object : LottieValueCallback<Array<Int>>() {
-                override fun getValue(frameInfo: LottieFrameInfo<Array<Int>>?): Array<Int>? {
+                override fun getValue(frameInfo: LottieFrameInfo<Array<Int>>?): Array<Int> {
                     return arrayOf(Color.YELLOW, Color.GREEN)
                 }
             }
@@ -994,6 +997,29 @@
         }
     }
 
+    private suspend fun testColorStateListColorFilter() {
+        log("Testing color filter")
+        val binding = TestColorFilterBinding.inflate(LayoutInflater.from(application))
+        val composition = LottieCompositionFactory.fromRawResSync(application, R.raw.solid).value!!
+
+        val bitmap = bitmapPool.acquire(1000, 1000)
+        val canvas = Canvas(bitmap)
+        val spec = View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY)
+        binding.root.measure(spec, spec)
+        binding.root.layout(0, 0, 1000, 1000)
+        binding.animationView.setComposition(composition)
+        canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR)
+        withContext(Dispatchers.Main) {
+            binding.root.draw(canvas)
+        }
+        LottieCompositionCache.getInstance().clear()
+        snapshotter.record(bitmap, "ColorFilter", "ColorStateList")
+        snapshotActivityRule.scenario.onActivity { activity ->
+            activity.recordSnapshot("ColorFilter", "ColorStateList")
+        }
+        bitmapPool.release(bitmap)
+    }
+
     private suspend fun withDrawable(assetName: String, snapshotName: String, snapshotVariant: String, callback: (LottieDrawable) -> Unit) {
         val result = LottieCompositionFactory.fromAssetSync(application, assetName)
         val composition = result.value
diff --git a/sample/src/main/res/color/test_color_filter.xml b/sample/src/main/res/color/test_color_filter.xml
new file mode 100644
index 0000000..c7934e6
--- /dev/null
+++ b/sample/src/main/res/color/test_color_filter.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="?attr/colorPrimary" />
+</selector>
\ No newline at end of file
diff --git a/sample/src/main/res/layout/test_color_filter.xml b/sample/src/main/res/layout/test_color_filter.xml
new file mode 100644
index 0000000..fb03ed7
--- /dev/null
+++ b/sample/src/main/res/layout/test_color_filter.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <com.airbnb.lottie.LottieAnimationView
+        android:id="@+id/animation_view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        app:lottie_autoPlay="true"
+        app:lottie_colorFilter="@color/test_color_filter"
+        app:lottie_rawRes="@raw/solid"
+        app:lottie_loop="true" />
+</FrameLayout>
\ No newline at end of file
diff --git a/sample/src/main/res/raw/solid.json b/sample/src/main/res/raw/solid.json
new file mode 100644
index 0000000..fd9ce34
--- /dev/null
+++ b/sample/src/main/res/raw/solid.json
@@ -0,0 +1 @@
+{"v":"5.7.4","fr":29.9700012207031,"ip":0,"op":900.000036657751,"w":200,"h":200,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":1,"nm":"Red Solid 1","sr":1,"ks":{"p":{"a":0,"k":[100,100,0],"ix":2,"l":2},"a":{"a":0,"k":[100,100,0],"ix":1,"l":2}},"ao":0,"sw":200,"sh":200,"sc":"#f30000","ip":0,"op":900.000036657751,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file