content: package com.airbnb.lottie;
  
  import android.content.Context;
  import android.content.res.Resources;
  import android.graphics.Rect;
  import android.os.AsyncTask;
  import android.support.annotation.Nullable;
  import android.support.v4.util.LongSparseArray;
  
  import org.json.JSONArray;
  import org.json.JSONException;
  import org.json.JSONObject;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import static com.airbnb.lottie.Utils.closeQuietly;
  
  /**
   * After Effects/Bodymovin composition model. This is the serialized model from which the
   * animation will be created.
   * It can be used with a {@link com.airbnb.lottie.LottieAnimationView} or
   * {@link com.airbnb.lottie.LottieDrawable}.
   */
  public class LottieComposition {
  
    private final Map<String, List<Layer>> precomps = new HashMap<>();
    private final Map<String, LottieImageAsset> images = new HashMap<>();
    private final LongSparseArray<Layer> layerMap = new LongSparseArray<>();
    private final List<Layer> layers = new ArrayList<>();
    private final Rect bounds;
    private final long startFrame;
    private final long endFrame;
    private final int frameRate;
    private final float dpScale;
  
    private LottieComposition(
        Rect bounds, long startFrame, long endFrame, int frameRate, float dpScale) {
      this.bounds = bounds;
      this.startFrame = startFrame;
      this.endFrame = endFrame;
      this.frameRate = frameRate;
      this.dpScale = dpScale;
    }
  
    Layer layerModelForId(long id) {
      return layerMap.get(id);
    }
  
    @SuppressWarnings("WeakerAccess") public Rect getBounds() {
      return bounds;
    }
  
    @SuppressWarnings("WeakerAccess") public long getDuration() {
      long frameDuration = endFrame - startFrame;
      return (long) (frameDuration / (float) frameRate * 1000);
    }
  
    long getEndFrame() {
      return endFrame;
    }
  
    List<Layer> getLayers() {
      return layers;
    }
  
    @Nullable
    List<Layer> getPrecomps(String id) {
      return precomps.get(id);
    }
  
    @SuppressWarnings("unused") boolean hasImages() {
      return !images.isEmpty();
    }
  
    Map<String, LottieImageAsset> getImages() {
      return images;
    }
  
    float getDurationFrames() {
      return getDuration() * (float) frameRate / 1000f;
    }
  
  
    public float getDpScale() {
      return dpScale;
    }
  
    @Override public String toString() {
      final StringBuilder sb = new StringBuilder("LottieComposition:\n");
      for (Layer layer : layers) {
        sb.append(layer.toString("\t"));
      }
      return sb.toString();
    }
  
    public static class Factory {
      private Factory() {
      }
  
      /**
       * Loads a composition from a file stored in /assets.
       */
      public static Cancellable fromAssetFileName(Context context, String fileName,
          OnCompositionLoadedListener loadedListener) {
        InputStream stream;
        try {
          stream = context.getAssets().open(fileName);
        } catch (IOException e) {
          throw new IllegalStateException("Unable to find file " + fileName, e);
        }
        return fromInputStream(context, stream, loadedListener);
      }
  
      /**
       * Loads a composition from an arbitrary input stream.
       * <p>
       * ex: fromInputStream(context, new FileInputStream(filePath), (composition) -> {});
       */
      public static Cancellable fromInputStream(Context context, InputStream stream,
          OnCompositionLoadedListener loadedListener) {
        FileCompositionLoader loader =
            new FileCompositionLoader(context.getResources(), loadedListener);
        loader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, stream);
        return loader;
      }
  
      static LottieComposition fromFileSync(Context context, String fileName) {
        InputStream stream;
        try {
          stream = context.getAssets().open(fileName);
        } catch (IOException e) {
          throw new IllegalStateException("Unable to find file " + fileName, e);
        }
        return fromInputStream(context.getResources(), stream);
      }
  
      /**
       * Loads a composition from a raw json object. This is useful for animations loaded from the
       * network.
       */
      public static Cancellable fromJson(Resources res, JSONObject json,
          OnCompositionLoadedListener loadedListener) {
        JsonCompositionLoader loader = new JsonCompositionLoader(res, loadedListener);
        loader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, json);
        return loader;
      }
  
      @SuppressWarnings("WeakerAccess")
      static LottieComposition fromInputStream(Resources res, InputStream stream) {
        try {
          // TODO: It's not correct to use available() to allocate the byte array.
          int size = stream.available();
          byte[] buffer = new byte[size];
          //noinspection ResultOfMethodCallIgnored
          stream.read(buffer);
          String json = new String(buffer, "UTF-8");
          JSONObject jsonObject = new JSONObject(json);
          return fromJsonSync(res, jsonObject);
        } catch (IOException e) {
          throw new IllegalStateException("Unable to find file.", e);
        } catch (JSONException e) {
          throw new IllegalStateException("Unable to load JSON.", e);
        } finally {
          closeQuietly(stream);
        }
      }
  
      @SuppressWarnings("WeakerAccess")
      static LottieComposition fromJsonSync(Resources res, JSONObject json) {
        Rect bounds = null;
        float scale = res.getDisplayMetrics().density;
        int width = json.optInt("w", -1);
        int height = json.optInt("h", -1);
  
        if (width != -1 && height != -1) {
          int scaledWidth = (int) (width * scale);
          int scaledHeight = (int) (height * scale);
          bounds = new Rect(0, 0, scaledWidth, scaledHeight);
        }
  
        long startFrame = json.optLong("ip", 0);
        long endFrame = json.optLong("op", 0);
        int frameRate = json.optInt("fr", 0);
        LottieComposition composition =
            new LottieComposition(bounds, startFrame, endFrame, frameRate, scale);
        JSONArray assetsJson = json.optJSONArray("assets");
        parseImages(assetsJson, composition);
        parsePrecomps(assetsJson, composition);
        parseLayers(json, composition);
        return composition;
      }
  
      private static void parseLayers(JSONObject json, LottieComposition composition) {
        JSONArray jsonLayers = json.optJSONArray("layers");
        int length = jsonLayers.length();
        for (int i = 0; i < length; i++) {
          Layer layer = Layer.Factory.newInstance(jsonLayers.optJSONObject(i), composition);
          addLayer(composition.layers, composition.layerMap, layer);
        }
      }
  
      private static void parsePrecomps(
          @Nullable JSONArray assetsJson, LottieComposition composition) {
        if (assetsJson == null) {
          return;
        }
        int length = assetsJson.length();
        for (int i = 0; i < length; i++) {
          JSONObject assetJson = assetsJson.optJSONObject(i);
          JSONArray layersJson = assetJson.optJSONArray("layers");
          if (layersJson == null) {
            continue;
          }
          List<Layer> layers = new ArrayList<>(layersJson.length());
          LongSparseArray<Layer> layerMap = new LongSparseArray<>();
          for (int j = 0; j < layersJson.length(); j++) {
            Layer layer = Layer.Factory.newInstance(layersJson.optJSONObject(j), composition);
            layerMap.put(layer.getId(), layer);
            layers.add(layer);
          }
          String id = assetJson.optString("id");
          composition.precomps.put(id, layers);
        }
      }
  
      private static void parseImages(
          @Nullable JSONArray assetsJson, LottieComposition composition) {
        if (assetsJson == null) {
          return;
        }
        int length = assetsJson.length();
        for (int i = 0; i < length; i++) {
          JSONObject assetJson = assetsJson.optJSONObject(i);
          if (!assetJson.has("p")) {
            continue;
          }
          LottieImageAsset image = LottieImageAsset.Factory.newInstance(assetJson);
          composition.images.put(image.getId(), image);
        }
      }
  
      private static void addLayer(List<Layer> layers, LongSparseArray<Layer> layerMap, Layer layer) {
        layers.add(layer);
        layerMap.put(layer.getId(), layer);
      }
    }
  }