diff --git a/lottie/src/main/java/com/airbnb/lottie/L.java b/lottie/src/main/java/com/airbnb/lottie/L.java index f26b6aeac8..834b7e7a7b 100644 --- a/lottie/src/main/java/com/airbnb/lottie/L.java +++ b/lottie/src/main/java/com/airbnb/lottie/L.java @@ -1,5 +1,16 @@ package com.airbnb.lottie; +import android.content.Context; + +import com.airbnb.lottie.network.LottieNetworkCacheProvider; +import com.airbnb.lottie.network.DefaultLottieNetworkFetcher; +import com.airbnb.lottie.network.LottieNetworkFetcher; +import com.airbnb.lottie.network.NetworkCache; +import com.airbnb.lottie.network.NetworkFetcher; + +import java.io.File; + +import androidx.annotation.NonNull; import androidx.annotation.RestrictTo; import androidx.core.os.TraceCompat; @@ -16,6 +27,12 @@ public class L { private static int traceDepth = 0; private static int depthPastMaxDepth = 0; + private static LottieNetworkFetcher fetcher; + private static LottieNetworkCacheProvider cacheProvider; + + private static volatile NetworkFetcher networkFetcher; + private static volatile NetworkCache networkCache; + public static void setTraceEnabled(boolean enabled) { if (traceEnabled == enabled) { return; @@ -60,4 +77,44 @@ public static float endSection(String section) { TraceCompat.endSection(); return (System.nanoTime() - startTimeNs[traceDepth]) / 1000000f; } + + public static void setFetcher(LottieNetworkFetcher customFetcher) { + fetcher = customFetcher; + } + + public static void setCacheProvider(LottieNetworkCacheProvider customProvider) { + cacheProvider = customProvider; + } + + @NonNull + public static NetworkFetcher networkFetcher(@NonNull Context context) { + NetworkFetcher local = networkFetcher; + if (local == null) { + synchronized (NetworkFetcher.class) { + local = networkFetcher; + if (local == null) { + networkFetcher = local = new NetworkFetcher(networkCache(context), fetcher != null ? fetcher : new DefaultLottieNetworkFetcher()); + } + } + } + return local; + } + + @NonNull + public static NetworkCache networkCache(@NonNull final Context context) { + NetworkCache local = networkCache; + if (local == null) { + synchronized (NetworkCache.class) { + local = networkCache; + if (local == null) { + networkCache = local = new NetworkCache(cacheProvider != null ? cacheProvider : new LottieNetworkCacheProvider() { + @Override @NonNull public File getCacheDir() { + return new File(context.getCacheDir(), "lottie_network_cache"); + } + }); + } + } + } + return local; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/Lottie.java b/lottie/src/main/java/com/airbnb/lottie/Lottie.java new file mode 100644 index 0000000000..96c7a6f255 --- /dev/null +++ b/lottie/src/main/java/com/airbnb/lottie/Lottie.java @@ -0,0 +1,17 @@ +package com.airbnb.lottie; + +import androidx.annotation.NonNull; + +/** + * Class for initializing the library with custom config + */ +public class Lottie { + + private Lottie() { + } + + public static void initialize(@NonNull final LottieConfig lottieConfig) { + L.setFetcher(lottieConfig.networkFetcher); + L.setCacheProvider(lottieConfig.cacheProvider); + } +} \ No newline at end of file diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java index 8d6fb95042..f4a1a6ab69 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java @@ -5,15 +5,13 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.os.Build; import com.airbnb.lottie.model.LottieCompositionCache; -import com.airbnb.lottie.network.NetworkCache; -import com.airbnb.lottie.network.NetworkFetcher; import com.airbnb.lottie.parser.LottieCompositionMoshiParser; import com.airbnb.lottie.parser.moshi.JsonReader; import com.airbnb.lottie.utils.Utils; + import org.json.JSONObject; import java.io.ByteArrayInputStream; @@ -65,14 +63,14 @@ public static void setMaxCacheSize(int size) { public static void clearCache(Context context) { taskCache.clear(); LottieCompositionCache.getInstance().clear(); - new NetworkCache(context).clear(); + L.networkCache(context).clear(); } /** * Fetch an animation from an http url. Once it is downloaded once, Lottie will cache the file to disk for * future use. Because of this, you may call `fromUrl` ahead of time to warm the cache if you think you * might need an animation in the future. - * + *
* To skip the cache, add null as a third parameter.
*/
public static LottieTask
* To skip the cache, add null as a third parameter.
*
* @see #fromZipStream(ZipInputStream, String)
@@ -132,7 +130,7 @@ public static LottieTask
* Pass null as the cache key to skip the cache.
*
* @see #fromZipStream(ZipInputStream, String)
@@ -152,22 +150,22 @@ public LottieResult
* To skip the cache, add null as a third parameter.
*
* @see #fromZipStreamSync(ZipInputStream, String)
*/
@WorkerThread
public static LottieResult
* Pass null as the cache key to skip the cache.
*
* @see #fromZipStreamSync(ZipInputStream, String)
@@ -191,7 +189,7 @@ public static LottieResult
* To skip the cache, add null as a third parameter.
*/
public static LottieTask
* Pass null as the cache key to skip caching.
*/
public static LottieTask
* To skip the cache, add null as a third parameter.
*/
@WorkerThread
@@ -241,7 +239,7 @@ public static LottieResult
* Pass null as the cache key to skip caching.
*/
@WorkerThread
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieConfig.java b/lottie/src/main/java/com/airbnb/lottie/LottieConfig.java
new file mode 100644
index 0000000000..2a560dcdf5
--- /dev/null
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieConfig.java
@@ -0,0 +1,69 @@
+package com.airbnb.lottie;
+
+import com.airbnb.lottie.network.LottieNetworkFetcher;
+import com.airbnb.lottie.network.LottieNetworkCacheProvider;
+
+import java.io.File;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Class for custom library configuration
+ */
+public class LottieConfig {
+
+ @Nullable final LottieNetworkFetcher networkFetcher;
+ @Nullable final LottieNetworkCacheProvider cacheProvider;
+
+ private LottieConfig(@Nullable LottieNetworkFetcher networkFetcher, @Nullable LottieNetworkCacheProvider cacheProvider) {
+ this.networkFetcher = networkFetcher;
+ this.cacheProvider = cacheProvider;
+ }
+
+ public static final class Builder {
+
+ @Nullable
+ private LottieNetworkFetcher networkFetcher;
+ @Nullable
+ private LottieNetworkCacheProvider cacheProvider;
+
+ @NonNull
+ public Builder setNetworkFetcher(@NonNull LottieNetworkFetcher fetcher) {
+ this.networkFetcher = fetcher;
+ return this;
+ }
+
+ @NonNull
+ public Builder setCacheDir(@NonNull final File file) {
+ cacheProvider = new LottieNetworkCacheProvider() {
+ @Override @NonNull public File getCacheDir() {
+ if (!file.isDirectory()) {
+ throw new IllegalArgumentException("cache file must be a directory");
+ }
+ return file;
+ }
+ };
+ return this;
+ }
+
+ @NonNull
+ public Builder setCacheProvider(@NonNull final LottieNetworkCacheProvider fileCacheProvider) {
+ cacheProvider = new LottieNetworkCacheProvider() {
+ @NonNull @Override public File getCacheDir() {
+ File file = fileCacheProvider.getCacheDir();
+ if (!file.isDirectory()) {
+ throw new IllegalArgumentException("cache file must be a directory");
+ }
+ return file;
+ }
+ };
+ return this;
+ }
+
+ @NonNull
+ public LottieConfig build() {
+ return new LottieConfig(networkFetcher, cacheProvider);
+ }
+ }
+}
\ No newline at end of file
diff --git a/lottie/src/main/java/com/airbnb/lottie/network/DefaultLottieFetchResult.java b/lottie/src/main/java/com/airbnb/lottie/network/DefaultLottieFetchResult.java
new file mode 100644
index 0000000000..9a408efe3b
--- /dev/null
+++ b/lottie/src/main/java/com/airbnb/lottie/network/DefaultLottieFetchResult.java
@@ -0,0 +1,73 @@
+package com.airbnb.lottie.network;
+
+import com.airbnb.lottie.utils.Logger;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class DefaultLottieFetchResult implements LottieFetchResult {
+
+ @NonNull
+ private final HttpURLConnection connection;
+
+ public DefaultLottieFetchResult(@NonNull HttpURLConnection connection) {
+ this.connection = connection;
+ }
+
+ @Override public boolean isSuccessful() {
+ try {
+ return connection.getResponseCode() / 100 == 2;
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ @NonNull @Override public InputStream bodyByteStream() throws IOException {
+ return connection.getInputStream();
+ }
+
+ @Nullable @Override public String contentType() {
+ return connection.getContentType();
+ }
+
+ @Nullable @Override public String error() {
+ try {
+ return isSuccessful() ? null :
+ "Unable to fetch " + connection.getURL() + ". Failed with " + connection.getResponseCode() + "\n" + getErrorFromConnection(connection);
+ } catch (IOException e) {
+ Logger.warning("get error failed ", e);
+ return e.getMessage();
+ }
+ }
+
+ @Override public void close() {
+ connection.disconnect();
+ }
+
+ private String getErrorFromConnection(HttpURLConnection connection) throws IOException {
+ BufferedReader r = new BufferedReader(new InputStreamReader(connection.getErrorStream()));
+ StringBuilder error = new StringBuilder();
+ String line;
+
+ try {
+ while ((line = r.readLine()) != null) {
+ error.append(line).append('\n');
+ }
+ } catch (Exception e) {
+ throw e;
+ } finally {
+ try {
+ r.close();
+ } catch (Exception e) {
+ // Do nothing.
+ }
+ }
+ return error.toString();
+ }
+}
diff --git a/lottie/src/main/java/com/airbnb/lottie/network/DefaultLottieNetworkFetcher.java b/lottie/src/main/java/com/airbnb/lottie/network/DefaultLottieNetworkFetcher.java
new file mode 100644
index 0000000000..68b2ac9d16
--- /dev/null
+++ b/lottie/src/main/java/com/airbnb/lottie/network/DefaultLottieNetworkFetcher.java
@@ -0,0 +1,19 @@
+package com.airbnb.lottie.network;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import androidx.annotation.NonNull;
+
+public class DefaultLottieNetworkFetcher implements LottieNetworkFetcher {
+
+ @Override
+ @NonNull
+ public LottieFetchResult fetchSync(@NonNull String url) throws IOException {
+ final HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
+ connection.setRequestMethod("GET");
+ connection.connect();
+ return new DefaultLottieFetchResult(connection);
+ }
+}
diff --git a/lottie/src/main/java/com/airbnb/lottie/network/LottieFetchResult.java b/lottie/src/main/java/com/airbnb/lottie/network/LottieFetchResult.java
new file mode 100644
index 0000000000..5a1a762125
--- /dev/null
+++ b/lottie/src/main/java/com/airbnb/lottie/network/LottieFetchResult.java
@@ -0,0 +1,39 @@
+package com.airbnb.lottie.network;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * The result of the operation of obtaining a Lottie animation
+ */
+public interface LottieFetchResult extends Closeable {
+ /**
+ * @return Is the operation successful
+ */
+ boolean isSuccessful();
+
+ /**
+ *
+ * @return Received content stream
+ */
+ @NonNull
+ InputStream bodyByteStream() throws IOException;
+
+ /**
+ *
+ * @return Type of content received
+ */
+ @Nullable
+ String contentType();
+
+ /**
+ *
+ * @return Operation error
+ */
+ @Nullable
+ String error();
+}
diff --git a/lottie/src/main/java/com/airbnb/lottie/network/LottieNetworkCacheProvider.java b/lottie/src/main/java/com/airbnb/lottie/network/LottieNetworkCacheProvider.java
new file mode 100644
index 0000000000..8258ba61c1
--- /dev/null
+++ b/lottie/src/main/java/com/airbnb/lottie/network/LottieNetworkCacheProvider.java
@@ -0,0 +1,20 @@
+package com.airbnb.lottie.network;
+
+
+import java.io.File;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Interface for providing the custom cache directory where animations downloaded via url are saved.
+ * @see com.airbnb.lottie.Lottie#initialize
+ */
+public interface LottieNetworkCacheProvider {
+
+ /**
+ * Called during cache operations
+ *
+ * @return cache directory
+ */
+ @NonNull File getCacheDir();
+}
\ No newline at end of file
diff --git a/lottie/src/main/java/com/airbnb/lottie/network/LottieNetworkFetcher.java b/lottie/src/main/java/com/airbnb/lottie/network/LottieNetworkFetcher.java
new file mode 100644
index 0000000000..48b7658221
--- /dev/null
+++ b/lottie/src/main/java/com/airbnb/lottie/network/LottieNetworkFetcher.java
@@ -0,0 +1,19 @@
+package com.airbnb.lottie.network;
+
+import java.io.IOException;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.WorkerThread;
+
+/**
+ * Implement this interface to handle network fetching manually when animations are requested via url. By default, Lottie will use an
+ * HttpUrlConnection under the hood but this enables you to hook into your own network stack. By default, Lottie will also handle caching the
+ * animations but if you want to provide your own cache directory, you may implement `LottieNetworkCacheProvider`.
+ *
+ * @see com.airbnb.lottie.Lottie#initialize
+ */
+public interface LottieNetworkFetcher {
+ @WorkerThread
+ @NonNull
+ LottieFetchResult fetchSync(@NonNull String url) throws IOException;
+}
diff --git a/lottie/src/main/java/com/airbnb/lottie/network/NetworkCache.java b/lottie/src/main/java/com/airbnb/lottie/network/NetworkCache.java
index 71efef2865..f0f29e132e 100644
--- a/lottie/src/main/java/com/airbnb/lottie/network/NetworkCache.java
+++ b/lottie/src/main/java/com/airbnb/lottie/network/NetworkCache.java
@@ -1,9 +1,11 @@
package com.airbnb.lottie.network;
-import android.content.Context;
+
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
-import androidx.core.util.Pair;
import com.airbnb.lottie.utils.Logger;
@@ -19,10 +21,12 @@
* Helper class to save and restore animations fetched from an URL to the app disk cache.
*/
public class NetworkCache {
- private final Context appContext;
- public NetworkCache(Context appContext) {
- this.appContext = appContext.getApplicationContext();
+ @NonNull
+ private final LottieNetworkCacheProvider cacheProvider;
+
+ public NetworkCache(@NonNull LottieNetworkCacheProvider cacheProvider) {
+ this.cacheProvider = cacheProvider;
}
public void clear() {
@@ -139,7 +143,7 @@ private File getCachedFile(String url) throws FileNotFoundException {
}
private File parentDir() {
- File file = new File(appContext.getCacheDir(), "lottie_network_cache");
+ File file = cacheProvider.getCacheDir();
if (file.isFile()) {
file.delete();
}
diff --git a/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java b/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java
index 2f7463373b..21fbc3400e 100644
--- a/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java
+++ b/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java
@@ -1,64 +1,52 @@
package com.airbnb.lottie.network;
-import android.content.Context;
-import androidx.annotation.Nullable;
-import androidx.annotation.WorkerThread;
-import androidx.core.util.Pair;
+import android.util.Pair;
import com.airbnb.lottie.LottieComposition;
import com.airbnb.lottie.LottieCompositionFactory;
import com.airbnb.lottie.LottieResult;
import com.airbnb.lottie.utils.Logger;
-import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.util.zip.ZipInputStream;
-public class NetworkFetcher {
+import java.util.zip.ZipInputStream;
- private final Context appContext;
- private final String url;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
- @Nullable private final NetworkCache networkCache;
+public class NetworkFetcher {
- public static LottieResult