From a855905cd54aa874b3124b4af8c089f08bbabbf4 Mon Sep 17 00:00:00 2001 From: Gabriel Peal Date: Wed, 27 Dec 2023 01:23:35 -0500 Subject: [PATCH] Add support for gzipped json files (#2435) --- .gitignore | 1 + .../lottie/LottieCompositionFactory.java | 39 ++++++++++++++---- .../lottie/snapshots/tests/AssetsTestCase.kt | 4 +- .../src/main/assets/Tests/winners.tgs | Bin 0 -> 14103 bytes 4 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 snapshot-tests/src/main/assets/Tests/winners.tgs diff --git a/.gitignore b/.gitignore index 3dc4fbf10c..0dbd2e3798 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ .idea/androidTestResultsUserPreferences.xml .idea/appInsightsSettings.xml .idea/migrations.xml +.idea/deploymentTargetSelector.xml # Gradle .idea/**/gradle.xml diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java index bfe55c38b6..42d9630d35 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java @@ -11,6 +11,7 @@ import android.graphics.BitmapFactory; import android.graphics.Typeface; import android.util.Base64; +import android.util.Log; import androidx.annotation.Nullable; import androidx.annotation.RawRes; @@ -41,7 +42,7 @@ import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.zip.GZIPInputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -57,6 +58,7 @@ */ @SuppressWarnings({"WeakerAccess", "unused", "NullAway"}) public class LottieCompositionFactory { + /** * Keep a map of cache keys to in-progress tasks and return them for new requests. * Without this, simultaneous requests to parse a composition will trigger multiple parallel @@ -69,7 +71,8 @@ public class LottieCompositionFactory { * reference magic bytes for zip compressed files. * useful to determine if an InputStream is a zip file or not */ - private static final byte[] MAGIC = new byte[]{0x50, 0x4b, 0x03, 0x04}; + private static final byte[] ZIP_MAGIC = new byte[]{0x50, 0x4b, 0x03, 0x04}; + private static final byte[] GZIP_MAGIC = new byte[]{0x1f, (byte) 0x8b, 0x08}; private LottieCompositionFactory() { @@ -222,10 +225,13 @@ public static LottieResult fromAssetSync(Context context, Str return new LottieResult<>(cachedComposition); } try { - if (fileName.endsWith(".zip") || fileName.endsWith(".lottie")) { - return fromZipStreamSync(context, new ZipInputStream(context.getAssets().open(fileName)), cacheKey); + BufferedSource source = Okio.buffer(source(context.getAssets().open(fileName))); + if (isZipCompressed(source)) { + return fromZipStreamSync(context, new ZipInputStream(source.inputStream()), cacheKey); + } else if (isGzipCompressed(source)) { + return fromJsonInputStreamSync(new GZIPInputStream(source.inputStream()), cacheKey); } - return fromJsonInputStreamSync(context.getAssets().open(fileName), cacheKey); + return fromJsonInputStreamSync(source.inputStream(), cacheKey); } catch (IOException e) { return new LottieResult<>(e); } @@ -298,6 +304,13 @@ public static LottieResult fromRawResSync(Context context, @R BufferedSource source = Okio.buffer(source(context.getResources().openRawResource(rawRes))); if (isZipCompressed(source)) { return fromZipStreamSync(context, new ZipInputStream(source.inputStream()), cacheKey); + } else if (isGzipCompressed(source)) { + try { + return fromJsonInputStreamSync(new GZIPInputStream(source.inputStream()), cacheKey); + } catch (IOException e) { + // This shouldn't happen because we check the header for magic bytes. + return new LottieResult<>(e); + } } return fromJsonInputStreamSync(source.inputStream(), cacheKey); } catch (Resources.NotFoundException e) { @@ -402,7 +415,8 @@ public static LottieResult fromJsonReaderSync(com.airbnb.lott } @WorkerThread - public static LottieResult fromJsonReaderSync(com.airbnb.lottie.parser.moshi.JsonReader reader, @Nullable String cacheKey, boolean close) { + public static LottieResult fromJsonReaderSync(com.airbnb.lottie.parser.moshi.JsonReader reader, @Nullable String cacheKey, + boolean close) { return fromJsonReaderSyncInternal(reader, cacheKey, close); } @@ -641,9 +655,20 @@ private static LottieResult fromZipStreamSyncInternal(Context * Check if a given InputStream points to a .zip compressed file */ private static Boolean isZipCompressed(BufferedSource inputSource) { + return matchesMagicBytes(inputSource, ZIP_MAGIC); + } + + /** + * Check if a given InputStream points to a .gzip compressed file + */ + private static Boolean isGzipCompressed(BufferedSource inputSource) { + return matchesMagicBytes(inputSource, GZIP_MAGIC); + } + + private static Boolean matchesMagicBytes(BufferedSource inputSource, byte[] magic) { try { BufferedSource peek = inputSource.peek(); - for (byte b : MAGIC) { + for (byte b : magic) { if (peek.readByte() != b) { return false; } diff --git a/snapshot-tests/src/androidTest/java/com/airbnb/lottie/snapshots/tests/AssetsTestCase.kt b/snapshot-tests/src/androidTest/java/com/airbnb/lottie/snapshots/tests/AssetsTestCase.kt index 39be041969..6267b395ea 100644 --- a/snapshot-tests/src/androidTest/java/com/airbnb/lottie/snapshots/tests/AssetsTestCase.kt +++ b/snapshot-tests/src/androidTest/java/com/airbnb/lottie/snapshots/tests/AssetsTestCase.kt @@ -31,7 +31,7 @@ class AssetsTestCase : SnapshotTestCase { listAssets(assets, pathWithPrefix) return@forEach } - if (!animation.endsWith(".json") && !animation.endsWith(".zip")) return@forEach + if (!animation.endsWith(".json") && !animation.endsWith(".zip") && !animation.endsWith(".tgs")) return@forEach assets += pathWithPrefix } return assets @@ -47,4 +47,4 @@ class AssetsTestCase : SnapshotTestCase { send(asset to composition) } } -} \ No newline at end of file +} diff --git a/snapshot-tests/src/main/assets/Tests/winners.tgs b/snapshot-tests/src/main/assets/Tests/winners.tgs new file mode 100644 index 0000000000000000000000000000000000000000..d1112ecbe732edca653edd932309de28c6393ea6 GIT binary patch literal 14103 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 VK3R8fCVlhA{|7}PTEIU30RV~bMGOD{ literal 0 HcmV?d00001