content: package com.airbnb.lottie;
  
  import android.graphics.Color;
  import android.support.annotation.Nullable;
  import android.util.Log;
  
  import org.json.JSONArray;
  import org.json.JSONObject;
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  import java.util.Locale;
  
  class Layer {
    private static final String TAG = Layer.class.getSimpleName();
  
    enum LayerType {
      PreComp,
      Solid,
      Image,
      Null,
      Shape,
      Text,
      Unknown
    }
  
    enum MatteType {
      None,
      Add,
      Invert,
      Unknown
    }
  
    private final List<Object> shapes;
    private final LottieComposition composition;
    private final String layerName;
    private final long layerId;
    private final LayerType layerType;
    private final long parentId;
    @Nullable private final String refId;
    private final List<Mask> masks;
    private final AnimatableTransform transform;
    private final int solidWidth;
    private final int solidHeight;
    private final int solidColor;
    private final float timeStretch;
    private final float startProgress;
    private final int preCompWidth;
    private final int preCompHeight;
    private final List<Keyframe<Float>> inOutKeyframes;
    private final MatteType matteType;
  
    private Layer(List<Object> shapes, LottieComposition composition, String layerName, long layerId,
        LayerType layerType, long parentId, @Nullable String refId, List<Mask> masks,
        AnimatableTransform transform, int solidWidth, int solidHeight, int solidColor,
        float timeStretch, float startProgress, int preCompWidth, int preCompHeight,
        List<Keyframe<Float>> inOutKeyframes, MatteType matteType) {
      this.shapes = shapes;
      this.composition = composition;
      this.layerName = layerName;
      this.layerId = layerId;
      this.layerType = layerType;
      this.parentId = parentId;
      this.refId = refId;
      this.masks = masks;
      this.transform = transform;
      this.solidWidth = solidWidth;
      this.solidHeight = solidHeight;
      this.solidColor = solidColor;
      this.timeStretch = timeStretch;
      this.startProgress = startProgress;
      this.preCompWidth = preCompWidth;
      this.preCompHeight = preCompHeight;
      this.inOutKeyframes = inOutKeyframes;
      this.matteType = matteType;
    }
  
    LottieComposition getComposition() {
      return composition;
    }
  
    float getTimeStretch() {
      return timeStretch;
    }
  
    float getStartProgress() {
      return startProgress;
    }
  
    List<Keyframe<Float>> getInOutKeyframes() {
      return inOutKeyframes;
    }
  
    long getId() {
      return layerId;
    }
  
    String getName() {
      return layerName;
    }
  
    @Nullable String getRefId() {
      return refId;
    }
  
    int getPreCompWidth() {
      return preCompWidth;
    }
  
    int getPreCompHeight() {
      return preCompHeight;
    }
  
    List<Mask> getMasks() {
      return masks;
    }
  
    LayerType getLayerType() {
      return layerType;
    }
  
    MatteType getMatteType() {
      return matteType;
    }
  
    long getParentId() {
      return parentId;
    }
  
    List<Object> getShapes() {
      return shapes;
    }
  
    AnimatableTransform getTransform() {
      return transform;
    }
  
    int getSolidColor() {
      return solidColor;
    }
  
    int getSolidHeight() {
      return solidHeight;
    }
  
    int getSolidWidth() {
      return solidWidth;
    }
  
    @Override public String toString() {
      return toString("");
    }
  
    String toString(String prefix) {
      StringBuilder sb = new StringBuilder();
      sb.append(prefix).append(getName()).append("\n");
      Layer parent = composition.layerModelForId(getParentId());
      if (parent != null) {
        sb.append("\t\tParents: ").append(parent.getName());
        parent = composition.layerModelForId(parent.getParentId());
        while (parent != null) {
          sb.append("->").append(parent.getName());
          parent = composition.layerModelForId(parent.getParentId());
        }
        sb.append(prefix).append("\n");
      }
      if (!getMasks().isEmpty()) {
        sb.append(prefix).append("\tMasks: ").append(getMasks().size()).append("\n");
      }
      if (getSolidWidth() != 0 && getSolidHeight() != 0) {
        sb.append(prefix).append("\tBackground: ").append(String
            .format(Locale.US, "%dx%d %X\n", getSolidWidth(), getSolidHeight(), getSolidColor()));
      }
      if (!shapes.isEmpty()) {
        sb.append(prefix).append("\tShapes:\n");
        for (Object shape : shapes) {
          sb.append(prefix).append("\t\t").append(shape).append("\n");
        }
      }
      return sb.toString();
    }
  
    static class Factory {
      private Factory() {
      }
  
      static Layer newInstance(LottieComposition composition) {
        // TODO: make sure in out keyframes work
        return new Layer(Collections.emptyList(), composition, null, -1, LayerType.PreComp, -1, null,
            Collections.<Mask>emptyList(), AnimatableTransform.Factory.newInstance(),
            0, 0, 0, 0, 0, 0, 0, Collections.<Keyframe<Float>>emptyList(), MatteType.None);
      }
  
      static Layer newInstance(JSONObject json, LottieComposition composition) {
        String layerName = json.optString("nm");
        String refId = json.optString("refId");
        long layerId = json.optLong("ind");
        int solidWidth = 0;
        int solidHeight = 0;
        int solidColor = 0;
        int preCompWidth = 0;
        int preCompHeight = 0;
        LayerType layerType;
        int layerTypeInt = json.optInt("ty", -1);
        if (layerTypeInt < LayerType.Unknown.ordinal()) {
          layerType = LayerType.values()[layerTypeInt];
        } else {
          layerType = LayerType.Unknown;
        }
  
        long parentId = json.optLong("parent", -1);
  
        if (layerType == LayerType.Solid) {
          solidWidth = (int) (json.optInt("sw") * composition.getDpScale());
          solidHeight = (int) (json.optInt("sh") * composition.getDpScale());
          solidColor = Color.parseColor(json.optString("sc"));
          if (L.DBG) {
            Log.d(TAG, "\tSolid=" + Integer.toHexString(solidColor) + " " +
                solidWidth + "x" + solidHeight + " " + composition.getBounds());
          }
        }
  
        AnimatableTransform transform = AnimatableTransform.Factory.newInstance(json.optJSONObject("ks"),
            composition);
        MatteType matteType = MatteType.values()[json.optInt("tt")];
        List<Object> shapes = new ArrayList<>();
        List<Mask> masks = new ArrayList<>();
        List<Keyframe<Float>> inOutKeyframes = new ArrayList<>();
        JSONArray jsonMasks = json.optJSONArray("masksProperties");
        if (jsonMasks != null) {
          for (int i = 0; i < jsonMasks.length(); i++) {
            Mask mask = Mask.Factory.newMask(jsonMasks.optJSONObject(i), composition);
            masks.add(mask);
          }
        }
  
        JSONArray shapesJson = json.optJSONArray("shapes");
        if (shapesJson != null) {
          for (int i = 0; i < shapesJson.length(); i++) {
            Object shape = ShapeGroup.shapeItemWithJson(shapesJson.optJSONObject(i), composition);
            if (shape != null) {
              shapes.add(shape);
            }
          }
        }
  
        float timeStretch = (float) json.optDouble("sr", 1.0);
        float startFrame = (float) json.optDouble("st");
        float frames = composition.getDurationFrames();
        float startProgress = startFrame / frames;
  
        if (layerType == LayerType.PreComp) {
          preCompWidth = (int) (json.optInt("w") * composition.getDpScale());
          preCompHeight = (int) (json.optInt("h") * composition.getDpScale());
        }
  
        float inFrame = json.optLong("ip");
        float outFrame = json.optLong("op");
  
        // Before the in frame
        if (inFrame > 0) {
          Keyframe<Float> preKeyframe = new Keyframe<>(composition, 0f, 0f, null, 0f, inFrame);
          inOutKeyframes.add(preKeyframe);
        }
  
        // The + 1 is because the animation should be visible on the out frame itself.
        outFrame = (outFrame > 0 ? outFrame : composition.getEndFrame() + 1);
        Keyframe<Float> visibleKeyframe =
            new Keyframe<>(composition, 1f, 1f, null, inFrame, outFrame);
        inOutKeyframes.add(visibleKeyframe);
  
        if (outFrame <= composition.getDurationFrames()) {
          Keyframe<Float> outKeyframe =
              new Keyframe<>(composition, 0f, 0f, null, outFrame, (float) composition.getEndFrame());
          inOutKeyframes.add(outKeyframe);
        }
  
        return new Layer(shapes, composition, layerName, layerId, layerType, parentId, refId,
            masks, transform, solidWidth, solidHeight, solidColor, timeStretch, startProgress,
            preCompWidth, preCompHeight, inOutKeyframes, matteType);
      }
    }
  }
  