content: package com.airbnb.lottie;
  
  import android.animation.Animator;
  import android.animation.ValueAnimator;
  import android.content.Context;
  import android.graphics.Bitmap;
  import android.graphics.Canvas;
  import android.graphics.ColorFilter;
  import android.graphics.Matrix;
  import android.graphics.PixelFormat;
  import android.graphics.drawable.Drawable;
  import android.support.annotation.FloatRange;
  import android.support.annotation.IntRange;
  import android.support.annotation.NonNull;
  import android.support.annotation.Nullable;
  import android.view.View;
  import android.view.animation.LinearInterpolator;
  
  /**
   * This can be used to show an lottie animation in any place that would normally take a drawable.
   * If there are masks or mattes, then you MUST call {@link #recycleBitmaps()} when you are done
   * or else you will leak bitmaps.
   * <p>
   * It is preferable to use {@link com.airbnb.lottie.LottieAnimationView} when possible because it
   * handles bitmap recycling and asynchronous loading
   * of compositions.
   */
  public class LottieDrawable extends Drawable implements Drawable.Callback {
    private final Matrix matrix = new Matrix();
    private LottieComposition composition;
    private final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
    private float speed = 0.7f;
    private float scale = 1.5f;
    private float progress = 0f;
  
    @Nullable private ImageAssetBitmapManager imageAssetBitmapManager;
    @Nullable private String imageAssetsFolder;
    @Nullable private ImageAssetDelegate imageAssetDelegate;
    private boolean playAnimationWhenCompositionAdded;
    private boolean reverseAnimationWhenCompositionAdded;
    private boolean systemAnimationsAreDisabled;
    @Nullable private CompositionLayer compositionLayer;
    private int alpha = 255;
  
    @SuppressWarnings("WeakerAccess") public LottieDrawable() {
      animator.setRepeatCount(0);
      animator.setInterpolator(new LinearInterpolator());
      animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override public void onAnimationUpdate(ValueAnimator animation) {
          if (systemAnimationsAreDisabled) {
            animator.cancel();
            setProgress(1f);
          } else {
            setProgress((float) animation.getAnimatedValue());
          }
        }
      });
    }
  
    /**
     * Returns whether or not any layers in this composition has masks.
     */
    @SuppressWarnings({"unused", "WeakerAccess"}) public boolean hasMasks() {
      return compositionLayer != null && compositionLayer.hasMasks();
    }
  
    /**
     * Returns whether or not any layers in this composition has a matte layer.
     */
    @SuppressWarnings({"unused", "WeakerAccess"}) public boolean hasMatte() {
      return compositionLayer != null && compositionLayer.hasMatte();
    }
  
    /**
     * If you use image assets, you must explicitly specify the folder in assets/ in which they are
     * located because bodymovin uses the name filenames across all compositions (img_#).
     * Do NOT rename the images themselves.
     *
     * If your images are located in src/main/assets/airbnb_loader/ then call
     * `setImageAssetsFolder("airbnb_loader/");`.
     *
     *
     * If you use LottieDrawable directly, you MUST call {@link #recycleBitmaps()} when you
     * are done. Calling {@link #recycleBitmaps()} doesn't have to be final and {@link LottieDrawable}
     * will recreate the bitmaps if needed but they will leak if you don't recycle them.
     */
    @SuppressWarnings("WeakerAccess") public void setImagesAssetsFolder(@Nullable String imageAssetsFolder) {
      this.imageAssetsFolder = imageAssetsFolder;
    }
  
    /**
     * If you have image assets and use {@link LottieDrawable} directly, you must call this yourself.
     *
     * Calling recycleBitmaps() doesn't have to be final and {@link LottieDrawable}
     * will recreate the bitmaps if needed but they will leak if you don't recycle them.
     *
     */
    @SuppressWarnings("WeakerAccess") public void recycleBitmaps() {
      if (imageAssetBitmapManager != null) {
        imageAssetBitmapManager.recycleBitmaps();
      }
    }
  
    /**
     * @return True if the composition is different from the previously set composition, false otherwise.
     */
    @SuppressWarnings("WeakerAccess") public boolean setComposition(LottieComposition composition) {
      if (getCallback() == null) {
        throw new IllegalStateException(
            "You or your view must set a Drawable.Callback before setting the composition. This " +
                "gets done automatically when added to an ImageView. " +
                "Either call ImageView.setImageDrawable() before setComposition() or call " +
                "setCallback(yourView.getCallback()) first.");
      }
  
      if (this.composition == composition) {
        return false;
      }
  
      clearComposition();
      this.composition = composition;
      setSpeed(speed);
      setScale(1f);
      updateBounds();
      compositionLayer = new CompositionLayer(
          this, Layer.Factory.newInstance(composition), composition.getLayers(), composition);
  
      setProgress(progress);
      if (playAnimationWhenCompositionAdded) {
        playAnimationWhenCompositionAdded = false;
        playAnimation();
      }
      if (reverseAnimationWhenCompositionAdded) {
        reverseAnimationWhenCompositionAdded = false;
        reverseAnimation();
      }
  
      return true;
    }
  
    private void clearComposition() {
      recycleBitmaps();
      compositionLayer = null;
      imageAssetBitmapManager = null;
      invalidateSelf();
    }
  
    @Override public void invalidateSelf() {
      final Callback callback = getCallback();
      if (callback != null) {
        callback.invalidateDrawable(this);
      }
    }
  
    @Override public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
      this.alpha = alpha;
    }
  
    @Override public int getAlpha() {
      return alpha;
    }
  
    @Override public void setColorFilter(@Nullable ColorFilter colorFilter) {
      // Do nothing.
    }
  
    @Override public int getOpacity() {
      return PixelFormat.TRANSLUCENT;
    }
  
    @Override public void draw(@NonNull Canvas canvas) {
      if (compositionLayer == null) {
        return;
      }
      matrix.reset();
      matrix.preScale(scale, scale);
      compositionLayer.draw(canvas, matrix, alpha);
    }
  
    void systemAnimationsAreDisabled() {
      systemAnimationsAreDisabled = true;
    }
  
    @SuppressWarnings("WeakerAccess") public void loop(boolean loop) {
      animator.setRepeatCount(loop ? ValueAnimator.INFINITE : 0);
    }
  
    @SuppressWarnings("WeakerAccess") public boolean isLooping() {
      return animator.getRepeatCount() == ValueAnimator.INFINITE;
    }
  
    @SuppressWarnings("WeakerAccess") public boolean isAnimating() {
      return animator.isRunning();
    }
  
    @SuppressWarnings("WeakerAccess") public void playAnimation() {
      playAnimation(false);
    }
  
    @SuppressWarnings("WeakerAccess") public void resumeAnimation() {
      playAnimation(true);
    }
  
    private void playAnimation(boolean setStartTime) {
      if (compositionLayer == null) {
        playAnimationWhenCompositionAdded = true;
        reverseAnimationWhenCompositionAdded = false;
        return;
      }
      if (setStartTime) {
        animator.setCurrentPlayTime((long) (progress * animator.getDuration()));
      }
      animator.start();
    }
  
    @SuppressWarnings({"unused", "WeakerAccess"}) public void resumeReverseAnimation() {
      reverseAnimation(true);
    }
  
    @SuppressWarnings("WeakerAccess") public void reverseAnimation() {
      reverseAnimation(false);
    }
  
    private void reverseAnimation(boolean setStartTime) {
      if (compositionLayer == null) {
        playAnimationWhenCompositionAdded = false;
        reverseAnimationWhenCompositionAdded = true;
        return;
      }
      if (setStartTime) {
        animator.setCurrentPlayTime((long) (progress * animator.getDuration()));
      }
      animator.reverse();
    }
  
    @SuppressWarnings("WeakerAccess") public void setSpeed(float speed) {
      this.speed = speed;
      if (speed < 0) {
        animator.setFloatValues(1f, 0f);
      } else {
        animator.setFloatValues(0f, 1f);
      }
  
      if (composition != null) {
        animator.setDuration((long) (composition.getDuration() / Math.abs(speed)));
      }
    }
  
    public void setProgress(@FloatRange(from = 0f, to = 1f) float progress) {
      this.progress = progress;
      if (compositionLayer != null) {
        compositionLayer.setProgress(progress);
      }
    }
  
    public float getProgress() {
      return progress;
    }
  
    @SuppressWarnings("WeakerAccess") public void setScale(float scale) {
      this.scale = scale;
      updateBounds();
    }
  
    /**
     * Use this if you can't bundle images with your app. This may be useful if you download the
     * animations from the network or have the images saved to an SD Card. In that case, Lottie
     * will defer the loading of the bitmap to this delegate.
     */
    @SuppressWarnings({"unused", "WeakerAccess"}) public void setImageAssetDelegate(
        @SuppressWarnings("NullableProblems") ImageAssetDelegate assetDelegate) {
      this.imageAssetDelegate = assetDelegate;
      if (imageAssetBitmapManager != null) {
        imageAssetBitmapManager.setAssetDelegate(assetDelegate);
      }
    }
  
    @SuppressWarnings("WeakerAccess") public float getScale() {
      return scale;
    }
  
    @SuppressWarnings("WeakerAccess") public LottieComposition getComposition() {
      return composition;
    }
  
    private void updateBounds() {
      if (composition == null) {
        return;
      }
      setBounds(0, 0, (int) (composition.getBounds().width() * scale),
          (int) (composition.getBounds().height() * scale));
    }
  
    @SuppressWarnings("WeakerAccess") public void cancelAnimation() {
      playAnimationWhenCompositionAdded = false;
      reverseAnimationWhenCompositionAdded = false;
      animator.cancel();
    }
  
    @SuppressWarnings("WeakerAccess") public void addAnimatorUpdateListener(ValueAnimator.AnimatorUpdateListener updateListener) {
      animator.addUpdateListener(updateListener);
    }
  
    @SuppressWarnings("WeakerAccess") public void removeAnimatorUpdateListener(ValueAnimator.AnimatorUpdateListener updateListener) {
      animator.removeUpdateListener(updateListener);
    }
  
    @SuppressWarnings("WeakerAccess") public void addAnimatorListener(Animator.AnimatorListener listener) {
      animator.addListener(listener);
    }
  
    @SuppressWarnings("WeakerAccess") public void removeAnimatorListener(Animator.AnimatorListener listener) {
      animator.removeListener(listener);
    }
  
    @Override public int getIntrinsicWidth() {
      return composition == null ? -1 : (int) (composition.getBounds().width() * scale);
    }
  
    @Override public int getIntrinsicHeight() {
      return composition == null ? -1 : (int) (composition.getBounds().height() * scale);
    }
  
    Bitmap getImageAsset(String id) {
      return getImageAssetBitmapManager().bitmapForId(id);
    }
  
    private ImageAssetBitmapManager getImageAssetBitmapManager() {
      if (imageAssetBitmapManager != null && !imageAssetBitmapManager.hasSameContext(getContext())) {
        imageAssetBitmapManager.recycleBitmaps();
        imageAssetBitmapManager = null;
      }
  
      if (imageAssetBitmapManager == null) {
        imageAssetBitmapManager = new ImageAssetBitmapManager(getCallback(),
            imageAssetsFolder, imageAssetDelegate, composition.getImages());
      }
  
      return imageAssetBitmapManager;
    }
  
    private @Nullable Context getContext() {
      Callback callback = getCallback();
      if (callback == null) {
        return null;
      }
  
      if (callback instanceof View) {
        return ((View) callback).getContext();
      }
      return null;
    }
  
    /**
     * These Drawable.Callback methods proxy the calls so that this is the drawable that is
     * actually invalidated, not a child one which will not pass the view's validateDrawable check.
     */
    @Override public void invalidateDrawable(@NonNull Drawable who) {
      Callback callback = getCallback();
      if (callback == null) {
        return;
      }
      callback.invalidateDrawable(this);
    }
  
    @Override public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
      Callback callback = getCallback();
      if (callback == null) {
        return;
      }
      callback.scheduleDrawable(this, what, when);
    }
  
    @Override public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
      Callback callback = getCallback();
      if (callback == null) {
        return;
      }
      callback.unscheduleDrawable(this, what);
    }
  }
  