Add on composition loaded listener (#928)

diff --git a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/DynamicActivity.kt b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/DynamicActivity.kt
index 88a5716..918c33a 100644
--- a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/DynamicActivity.kt
+++ b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/DynamicActivity.kt
@@ -3,22 +3,31 @@
 import android.graphics.PointF
 import android.os.Bundle
 import android.support.v7.app.AppCompatActivity
+import android.util.Log
 import com.airbnb.lottie.LottieProperty
 import com.airbnb.lottie.model.KeyPath
 import com.airbnb.lottie.utils.MiscUtils
-import kotlinx.android.synthetic.main.activity_dynamic.*
+import kotlinx.android.synthetic.main.activity_dynamic.animationView
+import kotlinx.android.synthetic.main.activity_dynamic.colorButton
+import kotlinx.android.synthetic.main.activity_dynamic.jumpHeight
+import kotlinx.android.synthetic.main.activity_dynamic.speedButton
 
 private val COLORS = arrayOf(
-        0xff5a5f,
-        0x008489,
-        0xa61d55
+    0xff5a5f,
+    0x008489,
+    0xa61d55
 )
 private val EXTRA_JUMP = arrayOf(0f, 20f, 50f)
+
 class DynamicActivity : AppCompatActivity() {
     private var speed = 1
     private var colorIndex = 0
     private var extraJumpIndex = 0
 
+    companion object {
+        val TAG = DynamicActivity::class.simpleName
+    }
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_dynamic)
@@ -38,13 +47,20 @@
             updateButtonText()
         }
 
+        animationView.addLottieOnCompositionLoadedListener { _ ->
+            animationView.resolveKeyPath(KeyPath("**")).forEach {
+                Log.d(TAG, it.keysToString())
+            }
+        }
+
         jumpHeight.postDelayed({ setupValueCallbacks() }, 1000)
         updateButtonText()
 
     }
 
     private fun setupValueCallbacks() {
-        animationView.addValueCallback(KeyPath("LeftArmWave"), LottieProperty.TIME_REMAP) { frameInfo ->
+        animationView.addValueCallback(KeyPath("LeftArmWave"),
+            LottieProperty.TIME_REMAP) { frameInfo ->
             2 * speed.toFloat() * frameInfo.overallProgress
         }
 
@@ -56,7 +72,8 @@
         animationView.addValueCallback(leftArm, LottieProperty.COLOR) { COLORS[colorIndex] }
         animationView.addValueCallback(rightArm, LottieProperty.COLOR) { COLORS[colorIndex] }
         val point = PointF()
-        animationView.addValueCallback(KeyPath("Body"), LottieProperty.TRANSFORM_POSITION) { frameInfo ->
+        animationView.addValueCallback(KeyPath("Body"),
+            LottieProperty.TRANSFORM_POSITION) { frameInfo ->
             val startX = frameInfo.startValue.x
             var startY = frameInfo.startValue.y
             var endY = frameInfo.endValue.y
diff --git a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/LottieApplication.kt b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/LottieApplication.kt
index 5bfcf82..4f5c462 100644
--- a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/LottieApplication.kt
+++ b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/LottieApplication.kt
@@ -35,6 +35,6 @@
 
     override fun onCreate() {
         super.onCreate()
-        L.DBG = true;
+        L.DBG = true
     }
 }
\ No newline at end of file
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java b/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
index fec5ee2..e4ea540 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
@@ -31,6 +31,7 @@
 import org.json.JSONObject;
 
 import java.io.StringReader;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
@@ -85,6 +86,7 @@
   private boolean wasAnimatingWhenDetached = false;
   private boolean autoPlay = false;
   private boolean useHardwareLayer = false;
+  private Set<LottieOnCompositionLoadedListener> lottieOnCompositionLoadedListeners = new HashSet<>();
 
   @Nullable private LottieTask compositionTask;
   /** Can be null because it is created async */
@@ -504,6 +506,11 @@
     setImageDrawable(lottieDrawable);
 
     requestLayout();
+
+    for (LottieOnCompositionLoadedListener lottieOnCompositionLoadedListener : lottieOnCompositionLoadedListeners) {
+        lottieOnCompositionLoadedListener.onCompositionLoaded(composition);
+    }
+
   }
 
   @Nullable public LottieComposition getComposition() {
@@ -808,18 +815,18 @@
     });
   }
 
-    /**
-     * Set the scale on the current composition. The only cost of this function is re-rendering the
-     * current frame so you may call it frequent to scale something up or down.
-     *
-     * The smaller the animation is, the better the performance will be. You may find that scaling an
-     * animation down then rendering it in a larger ImageView and letting ImageView scale it back up
-     * with a scaleType such as centerInside will yield better performance with little perceivable
-     * quality loss.
-     *
-     * You can also use a fixed view width/height in conjunction with the normal ImageView
-     * scaleTypes centerCrop and centerInside.
-     */
+  /**
+   * Set the scale on the current composition. The only cost of this function is re-rendering the
+   * current frame so you may call it frequent to scale something up or down.
+   *
+   * The smaller the animation is, the better the performance will be. You may find that scaling an
+   * animation down then rendering it in a larger ImageView and letting ImageView scale it back up
+   * with a scaleType such as centerInside will yield better performance with little perceivable
+   * quality loss.
+   *
+   * You can also use a fixed view width/height in conjunction with the normal ImageView
+   * scaleTypes centerCrop and centerInside.
+   */
   public void setScale(float scale) {
     lottieDrawable.setScale(scale);
     if (getDrawable() == lottieDrawable) {
@@ -891,6 +898,18 @@
     setLayerType(useHardwareLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_SOFTWARE, null);
   }
 
+  public boolean addLottieOnCompositionLoadedListener(@NonNull LottieOnCompositionLoadedListener lottieOnCompositionLoadedListener) {
+    return lottieOnCompositionLoadedListeners.add(lottieOnCompositionLoadedListener);
+  }
+
+  public boolean removeLottieOnCompositionLoadedListener(@NonNull LottieOnCompositionLoadedListener lottieOnCompositionLoadedListener) {
+    return lottieOnCompositionLoadedListeners.remove(lottieOnCompositionLoadedListener);
+  }
+
+  public void removeAllLottieOnCompositionLoadedListener() {
+    lottieOnCompositionLoadedListeners.clear();
+  }
+
   private static class SavedState extends BaseSavedState {
     String animationName;
     int animationResId;
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieOnCompositionLoadedListener.java b/lottie/src/main/java/com/airbnb/lottie/LottieOnCompositionLoadedListener.java
new file mode 100644
index 0000000..5cf1f88
--- /dev/null
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieOnCompositionLoadedListener.java
@@ -0,0 +1,5 @@
+package com.airbnb.lottie;
+
+public interface LottieOnCompositionLoadedListener {
+  void onCompositionLoaded(LottieComposition composition);
+}