Enable configuring a global idling resource
diff --git a/.gitignore b/.gitignore
index 1df66c6..ff3303c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,6 +30,7 @@
 .idea/modules.xml
 .idea/codeStyleSettings.xml
 .idea/compiler.xml
+.idea/androidTestResultsUserPreferences.xml
 
 # Gradle
 .idea/**/gradle.xml
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java
index a5d9da3..577599f 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java
@@ -33,10 +33,15 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.lang.ref.WeakReference;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 
@@ -58,6 +63,7 @@
    * parse tasks prior to the cache getting populated.
    */
   private static final Map<String, LottieTask<LottieComposition>> taskCache = new HashMap<>();
+  private static final Set<LottieTaskIdleListener> taskIdleListeners = new HashSet<>();
 
   /**
    * reference magic bytes for zip compressed files.
@@ -84,6 +90,21 @@
   }
 
   /**
+   * Use this to register a callback for when the composition factory is idle or not.
+   * This can be used to provide data to an espresso idling resource.
+   * Refer to FragmentVisibilityTests and its LottieIdlingResource in the Lottie repo for
+   * an example.
+   */
+  public static void registerLottieTaskIdleListener(LottieTaskIdleListener listener) {
+    taskIdleListeners.add(listener);
+    listener.onIdleChanged(taskCache.size() == 0);
+  }
+
+  public static void unregisterLottieTaskIdleListener(LottieTaskIdleListener listener) {
+    taskIdleListeners.remove(listener);
+  }
+
+  /**
    * Fetch an animation from an http url. Once it is downloaded once, Lottie will cache the file to disk for
    * future use. Because of this, you may call `fromUrl` ahead of time to warm the cache if you think you
    * might need an animation in the future.
@@ -596,10 +617,16 @@
       task.addListener(result -> {
         taskCache.remove(cacheKey);
         resultAlreadyCalled.set(true);
+        if (taskCache.size() == 0) {
+          notifyTaskCacheIdleListeners(true);
+        }
       });
       task.addFailureListener(result -> {
         taskCache.remove(cacheKey);
         resultAlreadyCalled.set(true);
+        if (taskCache.size() == 0) {
+          notifyTaskCacheIdleListeners(true);
+        }
       });
       // It is technically possible for the task to finish and for the listeners to get called
       // before this code runs. If this happens, the task will be put in taskCache but never removed.
@@ -607,8 +634,18 @@
       // for long enough for the task to finish and call the listeners. Unlikely but not impossible.
       if (!resultAlreadyCalled.get()) {
         taskCache.put(cacheKey, task);
+        if (taskCache.size() == 1) {
+          notifyTaskCacheIdleListeners(false);
+        }
       }
     }
     return task;
   }
+
+  private static void notifyTaskCacheIdleListeners(boolean idle) {
+    List<LottieTaskIdleListener> listeners = new ArrayList<>(taskIdleListeners);
+    for (int i = 0; i < listeners.size(); i++) {
+      listeners.get(i).onIdleChanged(idle);
+    }
+  }
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieTaskIdleListener.java b/lottie/src/main/java/com/airbnb/lottie/LottieTaskIdleListener.java
new file mode 100644
index 0000000..819ded0
--- /dev/null
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieTaskIdleListener.java
@@ -0,0 +1,5 @@
+package com.airbnb.lottie;
+
+public interface LottieTaskIdleListener {
+  void onIdleChanged(boolean idle);
+}
diff --git a/sample/src/androidTest/java/com/airbnb/lottie/samples/FragmentVisibilityTests.kt b/sample/src/androidTest/java/com/airbnb/lottie/samples/FragmentVisibilityTests.kt
index e4b586f..ca15b50 100644
--- a/sample/src/androidTest/java/com/airbnb/lottie/samples/FragmentVisibilityTests.kt
+++ b/sample/src/androidTest/java/com/airbnb/lottie/samples/FragmentVisibilityTests.kt
@@ -9,6 +9,7 @@
 import android.view.View
 import android.view.ViewGroup
 import android.widget.TextView
+import androidx.annotation.IdRes
 import androidx.appcompat.app.AlertDialog
 import androidx.core.view.isVisible
 import androidx.fragment.app.Fragment
@@ -29,6 +30,7 @@
 import com.airbnb.lottie.LottieDrawable
 import com.airbnb.lottie.model.LottieCompositionCache
 import com.nhaarman.mockitokotlin2.mock
+import org.junit.After
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
@@ -40,9 +42,18 @@
 @LargeTest
 class FragmentVisibilityTests {
 
+    lateinit var idlingResource: LottieIdlingResource
+
     @Before
     fun setup() {
         LottieCompositionCache.getInstance().clear()
+        idlingResource = LottieIdlingResource()
+        IdlingRegistry.getInstance().register(idlingResource)
+    }
+    
+    @After
+    fun teardown() {
+        IdlingRegistry.getInstance().unregister(idlingResource)
     }
 
     @Test
@@ -60,7 +71,6 @@
                             return object : RecyclerView.ViewHolder(LottieAnimationView(parent.context).apply {
                                 id = R.id.animation_view
                                 setAnimation(R.raw.heart)
-                                IdlingRegistry.getInstance().register(LottieIdlingResource(this))
                             }) {}
                         }
 
@@ -96,10 +106,6 @@
             override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
                 return inflater.inflate(R.layout.auto_play, container, false)
             }
-
-            override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-                IdlingRegistry.getInstance().register(LottieIdlingResource(view.findViewById(R.id.animation_view)))
-            }
         }
         launchFragmentInContainer<TestFragment>()
         onView(withId(R.id.animation_view)).check(matches(isAnimating()))
@@ -118,7 +124,6 @@
             override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
                 val animationView = requireView().findViewById<LottieAnimationView>(R.id.animation_view)
                 animationView.pauseAnimation()
-                IdlingRegistry.getInstance().register(LottieIdlingResource(animationView))
             }
         }
         launchFragmentInContainer<TestFragment>()
@@ -131,10 +136,6 @@
             override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
                 return inflater.inflate(R.layout.auto_play_gone, container, false)
             }
-
-            override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-                IdlingRegistry.getInstance().register(LottieIdlingResource(view.findViewById(R.id.animation_view)))
-            }
         }
 
         val scenario = launchFragmentInContainer<TestFragment>()
@@ -153,7 +154,6 @@
             }
 
             override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-                IdlingRegistry.getInstance().register(LottieIdlingResource(view.findViewById(R.id.animation_view)))
                 AlertDialog.Builder(requireContext()).setTitle("This is a dialog").show()
             }
         }
@@ -181,7 +181,6 @@
             override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
                 animationView = view.findViewById(R.id.animation_view)
                 animationView.addAnimatorListener(animationListener)
-                IdlingRegistry.getInstance().register(LottieIdlingResource(animationView))
             }
         }
 
@@ -226,7 +225,6 @@
                                 repeatMode = LottieDrawable.RESTART
                                 setAnimation(R.raw.heart)
                                 playAnimation()
-                                IdlingRegistry.getInstance().register(LottieIdlingResource(this))
                             }
                         }
                     }
@@ -244,10 +242,6 @@
             override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
                 return inflater.inflate(R.layout.no_auto_play, container, false)
             }
-
-            override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-                IdlingRegistry.getInstance().register(LottieIdlingResource(view.findViewById(R.id.animation_view)))
-            }
         }
         launchFragmentInContainer<TestFragment>()
         onView(withId(R.id.animation_view)).check(matches(isNotAnimating()))
@@ -259,10 +253,6 @@
             override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
                 return inflater.inflate(R.layout.auto_play, container, false)
             }
-
-            override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-                IdlingRegistry.getInstance().register(LottieIdlingResource(view.findViewById(R.id.animation_view)))
-            }
         }
 
         val scenario = launchFragmentInContainer<TestFragment>()
@@ -287,7 +277,6 @@
             override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
                 val animationView = view.findViewById<LottieAnimationView>(R.id.animation_view)
                 animationView.playAnimation()
-                IdlingRegistry.getInstance().register(LottieIdlingResource(animationView))
             }
         }
         launchFragmentInContainer<TestFragment>()
@@ -304,7 +293,6 @@
             override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
                 val animationView = view.findViewById<LottieAnimationView>(R.id.animation_view)
                 animationView.playAnimation()
-                IdlingRegistry.getInstance().register(LottieIdlingResource(animationView))
             }
         }
 
@@ -326,7 +314,6 @@
 
             override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
                 view.findViewById<View>(R.id.container).isVisible = false
-                IdlingRegistry.getInstance().register(LottieIdlingResource(view.findViewById(R.id.animation_view)))
             }
         }
 
@@ -373,7 +360,6 @@
                                     setAnimation(R.raw.heart)
                                     playAnimation()
                                     animationWasPlayed = true
-                                    IdlingRegistry.getInstance().register(LottieIdlingResource(this, name = "Lottie ${Random.nextFloat()}"))
                                 }
                             }
                         }
@@ -435,7 +421,6 @@
                                     setAnimation(R.raw.heart)
                                     playAnimation()
                                     animationWasPlayed = true
-                                    IdlingRegistry.getInstance().register(LottieIdlingResource(this, name = "Lottie ${Random.nextFloat()}"))
                                 }
                             }
                         }
@@ -469,10 +454,6 @@
             override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
                 return inflater.inflate(R.layout.auto_play, container, false)
             }
-
-            override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-                IdlingRegistry.getInstance().register(LottieIdlingResource(view.findViewById(R.id.animation_view)))
-            }
         }
 
         val scenario = launchFragmentInContainer<TestFragment>()
diff --git a/sample/src/androidTest/java/com/airbnb/lottie/samples/LottieIdlingResource.kt b/sample/src/androidTest/java/com/airbnb/lottie/samples/LottieIdlingResource.kt
index 42bf31e..0fdf100 100644
--- a/sample/src/androidTest/java/com/airbnb/lottie/samples/LottieIdlingResource.kt
+++ b/sample/src/androidTest/java/com/airbnb/lottie/samples/LottieIdlingResource.kt
@@ -1,22 +1,22 @@
 package com.airbnb.lottie.samples
 
-import androidx.test.espresso.IdlingRegistry
 import androidx.test.espresso.IdlingResource
-import com.airbnb.lottie.LottieAnimationView
+import com.airbnb.lottie.LottieCompositionFactory
 
-class LottieIdlingResource(animationView: LottieAnimationView? = null, private val name: String = "Lottie") : IdlingResource {
-
-    init {
-        animationView?.addLottieOnCompositionLoadedListener {
-            isIdle = true
-            callback?.onTransitionToIdle()
-            IdlingRegistry.getInstance().unregister(this)
-        }
-    }
+class LottieIdlingResource(private val name: String = "Lottie") : IdlingResource {
 
     private var callback: IdlingResource.ResourceCallback? = null
     private var isIdle = false
 
+    init {
+        LottieCompositionFactory.registerLottieTaskIdleListener { idle ->
+            isIdle = idle
+            if (idle) {
+                callback?.onTransitionToIdle()
+            }
+        }
+    }
+
     override fun getName() = name
 
     override fun isIdleNow() = isIdle