content: package com.airbnb.lottie;
  
  import android.graphics.PointF;
  import android.support.annotation.FloatRange;
  import android.support.annotation.Nullable;
  import android.support.v4.view.animation.PathInterpolatorCompat;
  import android.view.animation.Interpolator;
  import android.view.animation.LinearInterpolator;
  
  import org.json.JSONArray;
  import org.json.JSONObject;
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  
  class Keyframe<T> {
    private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
  
    /**
     * The json doesn't include end frames. The data can be taken from the start frame of the next
     * keyframe though.
     */
    static void setEndFrames(List<? extends Keyframe<?>> 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<?> lastKeyframe = keyframes.get(size - 1);
      if (lastKeyframe.startValue == null) {
        // The only purpose the last keyframe has is to provide the end frame of the previous
        // keyframe.
        //noinspection SuspiciousMethodCalls
        keyframes.remove(lastKeyframe);
      }
    }
  
  
    private final LottieComposition composition;
    @Nullable final T startValue;
    @Nullable final T endValue;
    @Nullable final Interpolator interpolator;
    @SuppressWarnings("WeakerAccess") final float startFrame;
    @SuppressWarnings("WeakerAccess") @Nullable Float endFrame;
  
    public Keyframe(LottieComposition composition, @Nullable T startValue, @Nullable T endValue,
        @Nullable Interpolator interpolator, float startFrame, @Nullable Float endFrame) {
      this.composition = composition;
      this.startValue = startValue;
      this.endValue = endValue;
      this.interpolator = interpolator;
      this.startFrame = startFrame;
      this.endFrame = endFrame;
    }
  
    @FloatRange(from = 0f, to = 1f)
    float getStartProgress() {
      return startFrame / composition.getDurationFrames();
    }
  
    @FloatRange(from = 0f, to = 1f)
    float getEndProgress() {
      //noinspection Range
      return endFrame == null ? 1f : endFrame / composition.getDurationFrames();
    }
  
    boolean isStatic() {
      return interpolator == null;
    }
  
    boolean containsProgress(@FloatRange(from = 0f, to = 1f) float progress) {
      return progress >= getStartProgress() && progress <= getEndProgress();
    }
  
    @Override public String toString() {
      return "Keyframe{" + "startValue=" + startValue +
          ", endValue=" + endValue +
          ", startFrame=" + startFrame +
          ", endFrame=" + endFrame +
          ", interpolator=" + interpolator +
          '}';
    }
  
    static class Factory {
      private Factory() {
      }
  
      static <T> Keyframe<T> newInstance(JSONObject json, LottieComposition composition, float scale,
          AnimatableValue.Factory<T> valueFactory) {
        PointF cp1 = null;
        PointF cp2 = null;
        float startFrame = 0;
        T startValue = null;
        T endValue = null;
        Interpolator interpolator = null;
  
        if (json.has("t")) {
          startFrame = (float) json.optDouble("t", 0);
          Object startValueJson = json.opt("s");
          if (startValueJson != null) {
            startValue = valueFactory.valueFromObject(startValueJson, scale);
          }
  
          Object endValueJson = json.opt("e");
          if (endValueJson != null) {
            endValue = valueFactory.valueFromObject(endValueJson, scale);
          }
  
          JSONObject cp1Json = json.optJSONObject("o");
          JSONObject cp2Json = json.optJSONObject("i");
          if (cp1Json != null && cp2Json != null) {
            cp1 = JsonUtils.pointFromJsonObject(cp1Json, scale);
            cp2 = JsonUtils.pointFromJsonObject(cp2Json, scale);
          }
  
          boolean hold = json.optInt("h", 0) == 1;
  
          if (hold) {
            endValue = startValue;
            // TODO: create a HoldInterpolator so progress changes don't invalidate.
            interpolator = LINEAR_INTERPOLATOR;
          } else if (cp1 != null) {
            interpolator = PathInterpolatorCompat.create(
                cp1.x / scale, cp1.y / scale, cp2.x / scale, cp2.y / scale);
          } else {
            interpolator = LINEAR_INTERPOLATOR;
          }
        } else {
          startValue = valueFactory.valueFromObject(json, scale);
          endValue = startValue;
        }
        return new Keyframe<>(composition, startValue, endValue, interpolator, startFrame, null);
      }
  
      static <T> List<Keyframe<T>> parseKeyframes(JSONArray json, LottieComposition composition,
          float scale, AnimatableValue.Factory<T> valueFactory) {
        int length = json.length();
        if (length == 0) {
          return Collections.emptyList();
        }
        List<Keyframe<T>> keyframes = new ArrayList<>();
        for (int i = 0; i < length; i++) {
          keyframes.add(Keyframe.Factory.newInstance(json.optJSONObject(i), composition, scale,
              valueFactory));
        }
  
        setEndFrames(keyframes);
        return keyframes;
      }
    }
  }
  