Support animations without end values in each keyframe (#1104)

diff --git a/LottieSample/src/androidTest/java/com/airbnb/lottie/LottieTest.kt b/LottieSample/src/androidTest/java/com/airbnb/lottie/LottieTest.kt
index 43c44d9..a9692b8 100644
--- a/LottieSample/src/androidTest/java/com/airbnb/lottie/LottieTest.kt
+++ b/LottieSample/src/androidTest/java/com/airbnb/lottie/LottieTest.kt
@@ -178,6 +178,7 @@
         filmStripView.setComposition(composition)
         canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR)
         withContext(Dispatchers.Main) {
+            log("Drawing $name")
             filmStripView.draw(canvas)
         }
         filmStripViewPool.release(filmStripView)
@@ -592,6 +593,7 @@
         callback(drawable)
         val bitmap = bitmapPool.acquire(drawable.intrinsicWidth, drawable.intrinsicHeight)
         val canvas = Canvas(bitmap)
+        log("Drawing $assetName")
         drawable.draw(canvas)
         snapshotter.record(bitmap, snapshotName, snapshotVariant)
         activity.recordSnapshot(snapshotName, snapshotVariant)
@@ -621,6 +623,7 @@
         animationViewContainer.layout(0, 0, animationViewContainer.measuredWidth, animationViewContainer.measuredHeight)
         val bitmap = bitmapPool.acquire(animationView.width, animationView.height)
         val canvas = Canvas(bitmap)
+        log("Drawing $assetName")
         animationView.draw(canvas)
         animationViewPool.release(animationView)
         snapshotter.record(bitmap, snapshotName, snapshotVariant)
diff --git a/LottieSample/src/main/assets/Tests/TransformWithoutEndValues.json b/LottieSample/src/main/assets/Tests/TransformWithoutEndValues.json
new file mode 100755
index 0000000..8b11e82
--- /dev/null
+++ b/LottieSample/src/main/assets/Tests/TransformWithoutEndValues.json
@@ -0,0 +1,251 @@
+{

+  "v": "4.8.0",

+  "fr": 30,

+  "ip": 0,

+  "op": 31,

+  "w": 1024,

+  "h": 768,

+  "nm": "new_json",

+  "ddd": 0,

+  "assets": [],

+  "layers": [

+    {

+      "ddd": 0,

+      "ind": 1,

+      "ty": 4,

+      "nm": "Shape Layer 2",

+      "sr": 1,

+      "ks": {

+        "o": {

+          "a": 0,

+          "k": 100,

+          "ix": 11

+        },

+        "r": {

+          "a": 0,

+          "k": 0,

+          "ix": 10

+        },

+        "p": {

+          "a": 1,

+          "k": [

+            {

+              "i": {

+                "x": 0.833,

+                "y": 0.833

+              },

+              "o": {

+                "x": 0.167,

+                "y": 0.167

+              },

+              "n": "0p833_0p833_0p167_0p167",

+              "t": 0,

+              "s": [

+                594,

+                474,

+                0

+              ],

+              "to": [

+                25.5,

+                25.8333339691162,

+                0

+              ],

+              "ti": [

+                -25.5,

+                -25.8333339691162,

+                0

+              ]

+            },

+            {

+              "t": 24,

+              "s": [

+                747,

+                629,

+                0

+              ]

+            }

+          ],

+          "ix": 2

+        },

+        "a": {

+          "a": 0,

+          "k": [

+            0,

+            0,

+            0

+          ],

+          "ix": 1

+        },

+        "s": {

+          "a": 0,

+          "k": [

+            100,

+            100,

+            100

+          ],

+          "ix": 6

+        }

+      },

+      "ao": 0,

+      "shapes": [

+        {

+          "ty": "gr",

+          "it": [

+            {

+              "ind": 0,

+              "ty": "sh",

+              "ix": 1,

+              "ks": {

+                "a": 0,

+                "k": {

+                  "i": [

+                    [

+                      0,

+                      0

+                    ],

+                    [

+                      0,

+                      0

+                    ],

+                    [

+                      0,

+                      0

+                    ],

+                    [

+                      0,

+                      0

+                    ]

+                  ],

+                  "o": [

+                    [

+                      0,

+                      0

+                    ],

+                    [

+                      0,

+                      0

+                    ],

+                    [

+                      0,

+                      0

+                    ],

+                    [

+                      0,

+                      0

+                    ]

+                  ],

+                  "v": [

+                    [

+                      -8.5,

+                      -23.5

+                    ],

+                    [

+                      -8.5,

+                      23.5

+                    ],

+                    [

+                      -59.5,

+                      23.5

+                    ],

+                    [

+                      -59.5,

+                      -23.5

+                    ]

+                  ],

+                  "c": true

+                },

+                "ix": 2

+              },

+              "nm": "Path 1",

+              "mn": "ADBE Vector Shape - Group",

+              "hd": false

+            },

+            {

+              "ty": "fl",

+              "c": {

+                "a": 0,

+                "k": [

+                  1,

+                  0,

+                  0,

+                  1

+                ],

+                "ix": 4

+              },

+              "o": {

+                "a": 0,

+                "k": 100,

+                "ix": 5

+              },

+              "r": 1,

+              "bm": 0,

+              "nm": "Fill 1",

+              "mn": "ADBE Vector Graphic - Fill",

+              "hd": false

+            },

+            {

+              "ty": "tr",

+              "p": {

+                "a": 0,

+                "k": [

+                  0,

+                  0

+                ],

+                "ix": 2

+              },

+              "a": {

+                "a": 0,

+                "k": [

+                  0,

+                  0

+                ],

+                "ix": 1

+              },

+              "s": {

+                "a": 0,

+                "k": [

+                  100,

+                  100

+                ],

+                "ix": 3

+              },

+              "r": {

+                "a": 0,

+                "k": 0,

+                "ix": 6

+              },

+              "o": {

+                "a": 0,

+                "k": 100,

+                "ix": 7

+              },

+              "sk": {

+                "a": 0,

+                "k": 0,

+                "ix": 4

+              },

+              "sa": {

+                "a": 0,

+                "k": 0,

+                "ix": 5

+              },

+              "nm": "Transform"

+            }

+          ],

+          "nm": "Group 1",

+          "np": 2,

+          "cix": 2,

+          "bm": 0,

+          "ix": 1,

+          "mn": "ADBE Vector Group",

+          "hd": false

+        }

+      ],

+      "ip": 0,

+      "op": 248,

+      "st": 0,

+      "bm": 0

+    }

+  ],

+  "markers": []

+}
\ No newline at end of file
diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/PathKeyframe.java b/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/PathKeyframe.java
index cc8f3b9..d9091e2 100644
--- a/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/PathKeyframe.java
+++ b/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/PathKeyframe.java
@@ -11,17 +11,23 @@
 public class PathKeyframe extends Keyframe<PointF> {
   @Nullable private Path path;
 
+  private final Keyframe<PointF> pointKeyFrame;
+
   public PathKeyframe(LottieComposition composition, Keyframe<PointF> keyframe) {
     super(composition, keyframe.startValue, keyframe.endValue, keyframe.interpolator,
         keyframe.startFrame, keyframe.endFrame);
+    this.pointKeyFrame = keyframe;
+    createPath();
+  }
 
+  public void createPath() {
     // This must use equals(float, float) because PointF didn't have an equals(PathF) method
     // until KitKat...
     boolean equals = endValue != null && startValue != null &&
         startValue.equals(endValue.x, endValue.y);
     //noinspection ConstantConditions
     if (endValue != null && !equals) {
-      path = Utils.createPath(startValue, endValue, keyframe.pathCp1, keyframe.pathCp2);
+      path = Utils.createPath(startValue, endValue, pointKeyFrame.pathCp1, pointKeyFrame.pathCp2);
     }
   }
 
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/AnimatableTransformParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/AnimatableTransformParser.java
index 0249dd4..39154c5 100644
--- a/lottie/src/main/java/com/airbnb/lottie/parser/AnimatableTransformParser.java
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/AnimatableTransformParser.java
@@ -74,6 +74,8 @@
           rotation = AnimatableValueParser.parseFloat(reader, composition, false);
           if (rotation.getKeyframes().isEmpty()) {
             rotation.getKeyframes().add(new Keyframe(composition, 0f, 0f, null, 0f, composition.getEndFrame()));
+          } else if (rotation.getKeyframes().get(0).startValue == null) {
+            rotation.getKeyframes().set(0, new Keyframe(composition, 0f, 0f, null, 0f, composition.getEndFrame()));
           }
           break;
         case "o":
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/KeyframesParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/KeyframesParser.java
index 64af1c5..24d91bc 100644
--- a/lottie/src/main/java/com/airbnb/lottie/parser/KeyframesParser.java
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/KeyframesParser.java
@@ -4,6 +4,7 @@
 import android.util.JsonToken;
 
 import com.airbnb.lottie.LottieComposition;
+import com.airbnb.lottie.animation.keyframe.PathKeyframe;
 import com.airbnb.lottie.value.Keyframe;
 
 import java.io.IOException;
@@ -33,8 +34,7 @@
 
             if (reader.peek() == JsonToken.NUMBER) {
               // For properties in which the static value is an array of numbers.
-              keyframes.add(
-                  KeyframeParser.parse(reader, composition, scale, valueParser, false));
+              keyframes.add(KeyframeParser.parse(reader, composition, scale, valueParser, false));
             } else {
               while (reader.hasNext()) {
                 keyframes.add(KeyframeParser.parse(reader, composition, scale, valueParser, true));
@@ -59,14 +59,22 @@
    * The json doesn't include end frames. The data can be taken from the start frame of the next
    * keyframe though.
    */
-  public static void setEndFrames(List<? extends Keyframe<?>> keyframes) {
+  public static <T> void setEndFrames(List<? extends Keyframe<T>> keyframes) {
     int size = keyframes.size();
     for (int i = 0; i < size - 1; i++) {
       // In the json, the keyframes only contain their starting frame.
-      keyframes.get(i).endFrame = keyframes.get(i + 1).startFrame;
+      Keyframe<T> keyframe = keyframes.get(i);
+      Keyframe<T> nextKeyframe = keyframes.get(i + 1);
+      keyframe.endFrame = nextKeyframe.startFrame;
+      if (keyframe.endValue == null && nextKeyframe.startValue != null) {
+        keyframe.endValue = nextKeyframe.startValue;
+        if (keyframe instanceof PathKeyframe) {
+          ((PathKeyframe) keyframe).createPath();
+        }
+      }
     }
     Keyframe<?> lastKeyframe = keyframes.get(size - 1);
-    if (lastKeyframe.startValue == null) {
+    if ((lastKeyframe.startValue == null || lastKeyframe.endValue == null) && keyframes.size() > 1) {
       // The only purpose the last keyframe has is to provide the end frame of the previous
       // keyframe.
       //noinspection SuspiciousMethodCalls
diff --git a/lottie/src/main/java/com/airbnb/lottie/value/Keyframe.java b/lottie/src/main/java/com/airbnb/lottie/value/Keyframe.java
index da3ea82..fa58a43 100644
--- a/lottie/src/main/java/com/airbnb/lottie/value/Keyframe.java
+++ b/lottie/src/main/java/com/airbnb/lottie/value/Keyframe.java
@@ -13,7 +13,7 @@
 
   @Nullable private final LottieComposition composition;
   @Nullable public final T startValue;
-  @Nullable public final T endValue;
+  @Nullable public T endValue;
   @Nullable public final Interpolator interpolator;
   public final float startFrame;
   @Nullable public Float endFrame;