content: package com.airbnb.lottie;
  
  import android.graphics.Canvas;
  import android.graphics.Matrix;
  import android.graphics.Paint;
  import android.graphics.Path;
  import android.graphics.PorterDuff;
  import android.graphics.PorterDuffXfermode;
  import android.graphics.RectF;
  import android.support.annotation.FloatRange;
  import android.support.annotation.Nullable;
  import android.util.Log;
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  
  abstract class BaseLayer implements DrawingContent, BaseKeyframeAnimation.AnimationListener {
    private static final int SAVE_FLAGS = Canvas.CLIP_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG |
        Canvas.MATRIX_SAVE_FLAG;
  
    static BaseLayer forModel(
      Layer layerModel, LottieDrawable drawable, LottieComposition composition) {
      switch (layerModel.getLayerType()) {
        case Shape:
          return new ShapeLayer(drawable, layerModel);
        case PreComp:
          return new CompositionLayer(drawable, layerModel,
              composition.getPrecomps(layerModel.getRefId()), composition);
        case Solid:
          return new SolidLayer(drawable, layerModel);
        case Image:
          return new ImageLayer(drawable, layerModel);
        case Null:
          return new NullLayer(drawable, layerModel);
        case Text:
        case Unknown:
        default:
          // Do nothing
          Log.w(L.TAG, "Unknown layer type " + layerModel.getLayerType());
          return new NullLayer(drawable, layerModel);
      }
    }
  
    private final Path path = new Path();
    private final Matrix matrix = new Matrix();
    private final Paint contentPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private final Paint maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private final Paint mattePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private final Paint clearPaint = new Paint();
    private final RectF rect = new RectF();
    final LottieDrawable lottieDrawable;
    final Layer layerModel;
    @Nullable private MaskKeyframeAnimation mask;
    @Nullable private BaseLayer matteLayer;
    @Nullable private BaseLayer parentLayer;
    private List<BaseLayer> parentLayers;
  
    private final List<BaseKeyframeAnimation<?, ?>> animations = new ArrayList<>();
    final TransformKeyframeAnimation transform;
    private boolean visible = true;
  
    BaseLayer(LottieDrawable lottieDrawable, Layer layerModel) {
      this.lottieDrawable = lottieDrawable;
      this.layerModel = layerModel;
      clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
      maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
      if (layerModel.getMatteType() == Layer.MatteType.Invert) {
        mattePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
      } else {
        mattePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
      }
  
      this.transform = layerModel.getTransform().createAnimation();
      transform.addListener(this);
      transform.addAnimationsToLayer(this);
  
      if (layerModel.getMasks() != null && !layerModel.getMasks().isEmpty()) {
        this.mask = new MaskKeyframeAnimation(layerModel.getMasks());
        for (BaseKeyframeAnimation<?, Path> animation : mask.getMaskAnimations()) {
          addAnimation(animation);
          animation.addUpdateListener(this);
        }
      }
      setupInOutAnimations();
    }
  
    @Override public void onValueChanged() {
      invalidateSelf();
    }
  
    Layer getLayerModel() {
      return layerModel;
    }
  
    void setMatteLayer(@Nullable BaseLayer matteLayer) {
      this.matteLayer = matteLayer;
    }
  
    boolean hasMatteOnThisLayer() {
      return matteLayer != null;
    }
  
    void setParentLayer(@Nullable BaseLayer parentLayer) {
      this.parentLayer = parentLayer;
    }
  
    private void setupInOutAnimations() {
      if (!layerModel.getInOutKeyframes().isEmpty()) {
        final FloatKeyframeAnimation inOutAnimation =
            new FloatKeyframeAnimation(layerModel.getInOutKeyframes());
        inOutAnimation.setIsDiscrete();
        inOutAnimation.addUpdateListener(new BaseKeyframeAnimation.AnimationListener() {
          @Override public void onValueChanged() {
            setVisible(inOutAnimation.getValue() == 1f);
          }
        });
        setVisible(inOutAnimation.getValue() == 1f);
        addAnimation(inOutAnimation);
      } else {
        setVisible(true);
      }
    }
  
    private void invalidateSelf() {
      lottieDrawable.invalidateSelf();
    }
  
    void addAnimation(BaseKeyframeAnimation<?, ?> newAnimation) {
      if (!(newAnimation instanceof StaticKeyframeAnimation)) {
        animations.add(newAnimation);
      }
    }
  
    @Override
    public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha) {
      if (!visible) {
        return;
      }
      buildParentLayerListIfNeeded();
      matrix.reset();
      matrix.set(parentMatrix);
      for (int i = parentLayers.size() - 1; i >= 0; i--) {
        matrix.preConcat(parentLayers.get(i).transform.getMatrix());
      }
      matrix.preConcat(transform.getMatrix());
      int alpha = (int)
          ((parentAlpha / 255f * (float) transform.getOpacity().getValue() / 100f) * 255);
      if (!hasMatteOnThisLayer() && !hasMasksOnThisLayer()) {
        drawLayer(canvas, matrix, alpha);
        return;
      }
  
      // TODO: make sure this is the right clip.
      rect.set(canvas.getClipBounds());
      canvas.saveLayer(rect, contentPaint, Canvas.ALL_SAVE_FLAG);
      // Clear the off screen buffer. This is necessary for some phones.
      canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), clearPaint);
      drawLayer(canvas, matrix, alpha);
  
      if (hasMasksOnThisLayer()) {
        applyMasks(canvas, matrix);
      }
  
      if (hasMatteOnThisLayer()) {
        canvas.saveLayer(rect, mattePaint, SAVE_FLAGS);
        canvas.drawRect(rect, clearPaint);
        //noinspection ConstantConditions
        matteLayer.draw(canvas, parentMatrix, alpha);
        canvas.restore();
      }
  
      canvas.restore();
    }
  
    abstract void drawLayer(Canvas canvas, Matrix parentMatrix, int parentAlpha);
  
    private void applyMasks(Canvas canvas, Matrix matrix) {
      canvas.saveLayer(rect, maskPaint, SAVE_FLAGS);
      canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), clearPaint);
  
      //noinspection ConstantConditions
      int size = mask.getMasks().size();
      for (int i = 0; i < size; i++) {
        Mask mask = this.mask.getMasks().get(i);
        BaseKeyframeAnimation<?, Path> maskAnimation = this.mask.getMaskAnimations().get(i);
        Path maskPath = maskAnimation.getValue();
        path.set(maskPath);
        path.transform(matrix);
  
        switch (mask.getMaskMode()) {
          case MaskModeSubtract:
            maskPath.setFillType(Path.FillType.INVERSE_WINDING);
            break;
          case MaskModeAdd:
          default:
            maskPath.setFillType(Path.FillType.WINDING);
        }
        canvas.drawPath(path, contentPaint);
      }
      canvas.restore();
    }
  
    boolean hasMasksOnThisLayer() {
      return mask != null && !mask.getMaskAnimations().isEmpty();
    }
  
    private void setVisible(boolean visible) {
      if (visible != this.visible) {
        this.visible = visible;
        invalidateSelf();
      }
    }
  
    void setProgress(@FloatRange(from = 0f, to = 1f) float progress) {
      if (matteLayer != null) {
        matteLayer.setProgress(progress);
      }
      for (int i = 0; i < animations.size(); i++) {
        animations.get(i).setProgress(progress);
      }
    }
  
    private void buildParentLayerListIfNeeded() {
      if (parentLayers != null) {
        return;
      }
      if (parentLayer == null) {
        parentLayers = Collections.emptyList();
        return;
      }
  
      parentLayers = new ArrayList<>();
      BaseLayer layer = parentLayer;
      while (layer != null) {
        parentLayers.add(layer);
        layer = layer.parentLayer;
      }
    }
  
    @Override public void setContents(List<Content> contentsBefore, List<Content> contentsAfter) {
      // Do nothing
    }
  }
  