content: package com.airbnb.lottie.animation.content;
  
  import android.graphics.Canvas;
  import android.graphics.Matrix;
  import android.graphics.Path;
  import android.graphics.RectF;
  
  import androidx.annotation.Nullable;
  
  import com.airbnb.lottie.LottieComposition;
  import com.airbnb.lottie.LottieDrawable;
  import com.airbnb.lottie.animation.keyframe.BaseKeyframeAnimation;
  import com.airbnb.lottie.animation.keyframe.TransformKeyframeAnimation;
  import com.airbnb.lottie.model.KeyPath;
  import com.airbnb.lottie.model.KeyPathElement;
  import com.airbnb.lottie.model.animatable.AnimatableTransform;
  import com.airbnb.lottie.model.content.ContentModel;
  import com.airbnb.lottie.model.content.ShapeGroup;
  import com.airbnb.lottie.model.layer.BaseLayer;
  import com.airbnb.lottie.utils.DropShadow;
  import com.airbnb.lottie.utils.OffscreenLayer;
  import com.airbnb.lottie.value.LottieValueCallback;
  
  import java.util.ArrayList;
  import java.util.List;
  
  public class ContentGroup implements DrawingContent, PathContent,
      BaseKeyframeAnimation.AnimationListener, KeyPathElement {
  
    private final OffscreenLayer.ComposeOp offscreenOp = new OffscreenLayer.ComposeOp();
    private final RectF offScreenRectF = new RectF();
    private final OffscreenLayer offscreenLayer = new OffscreenLayer();
  
    private static List<Content> contentsFromModels(LottieDrawable drawable, LottieComposition composition, BaseLayer layer,
        List<ContentModel> contentModels) {
      List<Content> contents = new ArrayList<>(contentModels.size());
      for (int i = 0; i < contentModels.size(); i++) {
        Content content = contentModels.get(i).toContent(drawable, composition, layer);
        if (content != null) {
          contents.add(content);
        }
      }
      return contents;
    }
  
    @Nullable static AnimatableTransform findTransform(List<ContentModel> contentModels) {
      for (int i = 0; i < contentModels.size(); i++) {
        ContentModel contentModel = contentModels.get(i);
        if (contentModel instanceof AnimatableTransform) {
          return (AnimatableTransform) contentModel;
        }
      }
      return null;
    }
  
    private final Matrix matrix = new Matrix();
    private final Path path = new Path();
    private final RectF rect = new RectF();
  
    private final String name;
    private final boolean hidden;
    private final List<Content> contents;
    private final LottieDrawable lottieDrawable;
    @Nullable private List<PathContent> pathContents;
    @Nullable private TransformKeyframeAnimation transformAnimation;
  
    public ContentGroup(final LottieDrawable lottieDrawable, BaseLayer layer, ShapeGroup shapeGroup, LottieComposition composition) {
      this(lottieDrawable, layer, shapeGroup.getName(),
          shapeGroup.isHidden(), contentsFromModels(lottieDrawable, composition, layer, shapeGroup.getItems()),
          findTransform(shapeGroup.getItems()));
    }
  
    ContentGroup(final LottieDrawable lottieDrawable, BaseLayer layer,
        String name, boolean hidden, List<Content> contents, @Nullable AnimatableTransform transform) {
      this.name = name;
      this.lottieDrawable = lottieDrawable;
      this.hidden = hidden;
      this.contents = contents;
  
      if (transform != null) {
        transformAnimation = transform.createAnimation();
        transformAnimation.addAnimationsToLayer(layer);
        transformAnimation.addListener(this);
      }
  
      List<GreedyContent> greedyContents = new ArrayList<>();
      for (int i = contents.size() - 1; i >= 0; i--) {
        Content content = contents.get(i);
        if (content instanceof GreedyContent) {
          greedyContents.add((GreedyContent) content);
        }
      }
  
      for (int i = greedyContents.size() - 1; i >= 0; i--) {
        greedyContents.get(i).absorbContent(contents.listIterator(contents.size()));
      }
    }
  
    @Override public void onValueChanged() {
      lottieDrawable.invalidateSelf();
    }
  
    @Override public String getName() {
      return name;
    }
  
    @Override public void setContents(List<Content> contentsBefore, List<Content> contentsAfter) {
      // Do nothing with contents after.
      List<Content> myContentsBefore = new ArrayList<>(contentsBefore.size() + contents.size());
      myContentsBefore.addAll(contentsBefore);
  
      for (int i = contents.size() - 1; i >= 0; i--) {
        Content content = contents.get(i);
        content.setContents(myContentsBefore, contents.subList(0, i));
        myContentsBefore.add(content);
      }
    }
  
    public List<Content> getContents() {
      return contents;
    }
  
    List<PathContent> getPathList() {
      if (pathContents == null) {
        pathContents = new ArrayList<>();
        for (int i = 0; i < contents.size(); i++) {
          Content content = contents.get(i);
          if (content instanceof PathContent) {
            pathContents.add((PathContent) content);
          }
        }
      }
      return pathContents;
    }
  
    Matrix getTransformationMatrix() {
      if (transformAnimation != null) {
        return transformAnimation.getMatrix();
      }
      matrix.reset();
      return matrix;
    }
  
    @Override public Path getPath() {
      // TODO: cache this somehow.
      matrix.reset();
      if (transformAnimation != null) {
        matrix.set(transformAnimation.getMatrix());
      }
      path.reset();
      if (hidden) {
        return path;
      }
      for (int i = contents.size() - 1; i >= 0; i--) {
        Content content = contents.get(i);
        if (content instanceof PathContent) {
          path.addPath(((PathContent) content).getPath(), matrix);
        }
      }
      return path;
    }
  
    @Override public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha, @Nullable DropShadow shadowToApply) {
      if (hidden) {
        return;
      }
      matrix.set(parentMatrix);
      int layerAlpha;
      if (transformAnimation != null) {
        matrix.preConcat(transformAnimation.getMatrix());
        int opacity = transformAnimation.getOpacity() == null ? 100 : transformAnimation.getOpacity().getValue();
        layerAlpha = (int) ((opacity / 100f * parentAlpha / 255f) * 255);
      } else {
        layerAlpha = parentAlpha;
      }
  
      // Apply off-screen rendering only when needed in order to improve rendering performance.
      boolean isRenderingWithOffScreen =
          (lottieDrawable.isApplyingOpacityToLayersEnabled() && hasTwoOrMoreDrawableContent() && layerAlpha != 255) ||
          (shadowToApply != null && lottieDrawable.isApplyingShadowToLayersEnabled() && hasTwoOrMoreDrawableContent());
      int childAlpha = isRenderingWithOffScreen ? 255 : layerAlpha;
  
      Canvas contentCanvas = canvas;
      if (isRenderingWithOffScreen) {
        offScreenRectF.set(0, 0, 0, 0);
        getBounds(offScreenRectF, parentMatrix, true);
        offscreenOp.alpha = layerAlpha;
        if (shadowToApply != null) {
          shadowToApply.applyTo(offscreenOp);
          shadowToApply = null; // Don't pass it to children - OffscreenLayer now takes care of this
        } else {
          offscreenOp.shadow = null;
        }
  
        contentCanvas = offscreenLayer.start(canvas, offScreenRectF, offscreenOp);
      } else {
        if (shadowToApply != null) {
          shadowToApply = new DropShadow(shadowToApply);
          shadowToApply.multiplyOpacity(childAlpha);
        }
      }
  
      for (int i = contents.size() - 1; i >= 0; i--) {
        Object content = contents.get(i);
        if (content instanceof DrawingContent) {
          ((DrawingContent) content).draw(contentCanvas, matrix, childAlpha, shadowToApply);
        }
      }
  
      if (isRenderingWithOffScreen) {
        offscreenLayer.finish();
      }
    }
  
    private boolean hasTwoOrMoreDrawableContent() {
      int drawableContentCount = 0;
      for (int i = 0; i < contents.size(); i++) {
        if (contents.get(i) instanceof DrawingContent) {
          drawableContentCount += 1;
          if (drawableContentCount >= 2) {
            return true;
          }
        }
      }
      return false;
    }
  
    @Override public void getBounds(RectF outBounds, Matrix parentMatrix, boolean applyParents) {
      matrix.set(parentMatrix);
      if (transformAnimation != null) {
        matrix.preConcat(transformAnimation.getMatrix());
      }
      rect.set(0, 0, 0, 0);
      for (int i = contents.size() - 1; i >= 0; i--) {
        Content content = contents.get(i);
        if (content instanceof DrawingContent) {
          ((DrawingContent) content).getBounds(rect, matrix, applyParents);
          outBounds.union(rect);
        }
      }
    }
  
    @Override public void resolveKeyPath(
        KeyPath keyPath, int depth, List<KeyPath> accumulator, KeyPath currentPartialKeyPath) {
      if (!keyPath.matches(getName(), depth) && !"__container".equals(getName())) {
        return;
      }
  
      if (!"__container".equals(getName())) {
        currentPartialKeyPath = currentPartialKeyPath.addKey(getName());
  
        if (keyPath.fullyResolvesTo(getName(), depth)) {
          accumulator.add(currentPartialKeyPath.resolve(this));
        }
      }
  
      if (keyPath.propagateToChildren(getName(), depth)) {
        int newDepth = depth + keyPath.incrementDepthBy(getName(), depth);
        for (int i = 0; i < contents.size(); i++) {
          Content content = contents.get(i);
          if (content instanceof KeyPathElement) {
            KeyPathElement element = (KeyPathElement) content;
            element.resolveKeyPath(keyPath, newDepth, accumulator, currentPartialKeyPath);
          }
        }
      }
    }
  
    @Override
    public <T> void addValueCallback(T property, @Nullable LottieValueCallback<T> callback) {
      if (transformAnimation != null) {
        transformAnimation.applyValueCallback(property, callback);
      }
    }
  }
  