Add a safeMode API (#1449)

This allows you to wrap draw with a try/catch for specific problematic devices. Please read its doc before using.

#1422
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java b/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
index 757a569..4e0e063 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
@@ -912,6 +912,21 @@
   }
 
   /**
+   * If you are experiencing a device specific crash that happens during drawing, you can set this to true
+   * for those devices. If set to true, draw will be wrapped with a try/catch which will cause Lottie to
+   * render an empty frame rather than crash your app.
+   *
+   * Ideally, you will never need this and the vast majority of apps and animations won't. However, you may use
+   * this for very specific cases if absolutely necessary.
+   *
+   * There is no XML attr for this because it should be set programmatically and only for specific devices that
+   * are known to be problematic.
+   */
+  public void setSafeMode(boolean safeMode) {
+    lottieDrawable.setSafeMode(safeMode);
+  }
+
+  /**
    * If rendering via software, Android will fail to generate a bitmap if the view is too large. Rather than displaying
    * nothing, fallback on hardware acceleration which may incur a performance hit.
    *
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java b/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
index c17cdf3..2cd16ab 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
@@ -63,6 +63,7 @@
   private final LottieValueAnimator animator = new LottieValueAnimator();
   private float scale = 1f;
   private boolean systemAnimationsEnabled = true;
+  private boolean safeMode = false;
 
   private final Set<ColorFilterData> colorFilterData = new HashSet<>();
   private final ArrayList<LazyCompositionTask> lazyCompositionTasks = new ArrayList<>();
@@ -300,6 +301,18 @@
     invalidateSelf();
   }
 
+  /**
+   * If you are experiencing a device specific crash that happens during drawing, you can set this to true
+   * for those devices. If set to true, draw will be wrapped with a try/catch which will cause Lottie to
+   * render an empty frame rather than crash your app.
+   *
+   * Ideally, you will never need this and the vast majority of apps and animations won't. However, you may use
+   * this for very specific cases if absolutely necessary.
+   */
+  public void setSafeMode(boolean safeMode) {
+    this.safeMode = safeMode;
+  }
+
   @Override
   public void invalidateSelf() {
     if (isDirty) {
@@ -339,13 +352,25 @@
 
     L.beginSection("Drawable#draw");
 
+    if (safeMode) {
+      try {
+        drawInternal(canvas);
+      } catch (Throwable e) {
+        Logger.error("Lottie crashed in draw!", e);
+      }
+    } else {
+      drawInternal(canvas);
+    }
+
+    L.endSection("Drawable#draw");
+  }
+
+  private void drawInternal(@NonNull Canvas canvas) {
     if (ImageView.ScaleType.FIT_XY == scaleType) {
       drawWithNewAspectRatio(canvas);
     } else {
       drawWithOriginalAspectRatio(canvas);
     }
-
-    L.endSection("Drawable#draw");
   }
 
 // <editor-fold desc="animator">
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieLogger.java b/lottie/src/main/java/com/airbnb/lottie/LottieLogger.java
index 638b162..7b36ed4 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieLogger.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieLogger.java
@@ -12,4 +12,6 @@
   void warning(String message);
 
   void warning(String message, Throwable exception);
+
+  void error(String message, Throwable exception);
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/utils/LogcatLogger.java b/lottie/src/main/java/com/airbnb/lottie/utils/LogcatLogger.java
index ad166a5..1bacdca 100644
--- a/lottie/src/main/java/com/airbnb/lottie/utils/LogcatLogger.java
+++ b/lottie/src/main/java/com/airbnb/lottie/utils/LogcatLogger.java
@@ -43,4 +43,10 @@
 
     loggedMessages.add(message);
   }
+
+  @Override public void error(String message, Throwable exception) {
+    if (L.DBG) {
+      Log.d(L.TAG, message, exception);
+    }
+  }
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/utils/Logger.java b/lottie/src/main/java/com/airbnb/lottie/utils/Logger.java
index acf4ef7..cf57baf 100644
--- a/lottie/src/main/java/com/airbnb/lottie/utils/Logger.java
+++ b/lottie/src/main/java/com/airbnb/lottie/utils/Logger.java
@@ -29,4 +29,8 @@
   public static void warning(String message, Throwable exception) {
     INSTANCE.warning(message, exception);
   }
+
+  public static void error(String message, Throwable exception) {
+    INSTANCE.error(message, exception);
+  }
 }