diff --git a/libraries/datasource/src/androidTest/java/androidx/media3/datasource/DataSourceBitmapLoaderTest.java b/libraries/datasource/src/androidTest/java/androidx/media3/datasource/DataSourceBitmapLoaderTest.java index e3c007897b..521500f5e5 100644 --- a/libraries/datasource/src/androidTest/java/androidx/media3/datasource/DataSourceBitmapLoaderTest.java +++ b/libraries/datasource/src/androidTest/java/androidx/media3/datasource/DataSourceBitmapLoaderTest.java @@ -23,6 +23,7 @@ import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.net.Uri; import androidx.media3.common.MediaMetadata; +import androidx.media3.common.ParserException; import androidx.media3.test.utils.TestUtil; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -114,9 +115,7 @@ public class DataSourceBitmapLoaderTest { ListenableFuture future = bitmapLoader.decodeBitmap(new byte[0]); assertException( - future::get, - IllegalArgumentException.class, - /* messagePart= */ "Could not decode image data"); + future::get, ParserException.class, /* messagePart= */ "Could not decode image data"); } @Test diff --git a/libraries/datasource/src/main/java/androidx/media3/datasource/BitmapUtil.java b/libraries/datasource/src/main/java/androidx/media3/datasource/BitmapUtil.java new file mode 100644 index 0000000000..6ca102094b --- /dev/null +++ b/libraries/datasource/src/main/java/androidx/media3/datasource/BitmapUtil.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.media3.datasource; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; +import androidx.annotation.Nullable; +import androidx.exifinterface.media.ExifInterface; +import androidx.media3.common.ParserException; +import androidx.media3.common.util.UnstableApi; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** Utility methods for {@link Bitmap} instances. */ +@UnstableApi +public final class BitmapUtil { + + private BitmapUtil() {} + + /** + * Decodes a {@link Bitmap} from a byte array using {@link BitmapFactory} and the {@link + * ExifInterface}. + * + * @param data Byte array of compressed image data. + * @param length The number of bytes to parse. + * @param options the {@link BitmapFactory.Options} to decode the {@code data} with. + * @throws ParserException if the {@code data} could not be decoded. + */ + // BitmapFactory's options parameter is null-ok. + @SuppressWarnings("nullness:argument.type.incompatible") + public static Bitmap decode(byte[] data, int length, @Nullable BitmapFactory.Options options) + throws IOException { + @Nullable Bitmap bitmap = BitmapFactory.decodeByteArray(data, /* offset= */ 0, length, options); + if (bitmap == null) { + throw ParserException.createForMalformedContainer( + "Could not decode image data", new IllegalStateException()); + } + // BitmapFactory doesn't read the exif header, so we use the ExifInterface to this do ensure the + // bitmap is correctly orientated. + ExifInterface exifInterface; + try (InputStream inputStream = new ByteArrayInputStream(data)) { + exifInterface = new ExifInterface(inputStream); + } + int rotationDegrees = exifInterface.getRotationDegrees(); + if (rotationDegrees != 0) { + Matrix matrix = new Matrix(); + matrix.postRotate(rotationDegrees); + bitmap = + Bitmap.createBitmap( + bitmap, + /* x= */ 0, + /* y= */ 0, + bitmap.getWidth(), + bitmap.getHeight(), + matrix, + /* filter= */ false); + } + return bitmap; + } +} diff --git a/libraries/datasource/src/main/java/androidx/media3/datasource/DataSourceBitmapLoader.java b/libraries/datasource/src/main/java/androidx/media3/datasource/DataSourceBitmapLoader.java index fdd8d88467..95efd0157b 100644 --- a/libraries/datasource/src/main/java/androidx/media3/datasource/DataSourceBitmapLoader.java +++ b/libraries/datasource/src/main/java/androidx/media3/datasource/DataSourceBitmapLoader.java @@ -96,7 +96,7 @@ public final class DataSourceBitmapLoader implements BitmapLoader { @Override public ListenableFuture decodeBitmap(byte[] data) { - return listeningExecutorService.submit(() -> DataSourceUtil.decode(data, data.length, options)); + return listeningExecutorService.submit(() -> BitmapUtil.decode(data, data.length, options)); } @Override @@ -111,7 +111,7 @@ public final class DataSourceBitmapLoader implements BitmapLoader { DataSpec dataSpec = new DataSpec(uri); dataSource.open(dataSpec); byte[] readData = DataSourceUtil.readToEnd(dataSource); - return DataSourceUtil.decode(readData, readData.length, options); + return BitmapUtil.decode(readData, readData.length, options); } finally { dataSource.close(); } diff --git a/libraries/datasource/src/main/java/androidx/media3/datasource/DataSourceUtil.java b/libraries/datasource/src/main/java/androidx/media3/datasource/DataSourceUtil.java index b8ea89a934..aa14615c8e 100644 --- a/libraries/datasource/src/main/java/androidx/media3/datasource/DataSourceUtil.java +++ b/libraries/datasource/src/main/java/androidx/media3/datasource/DataSourceUtil.java @@ -15,24 +15,15 @@ */ package androidx.media3.datasource; -import static androidx.media3.common.util.Assertions.checkArgument; - -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Matrix; import androidx.annotation.Nullable; -import androidx.exifinterface.media.ExifInterface; import androidx.media3.common.C; import androidx.media3.common.util.UnstableApi; -import java.io.ByteArrayInputStream; import java.io.IOException; -import java.io.InputStream; import java.util.Arrays; /** Utility methods for {@link DataSource}. */ @UnstableApi public final class DataSourceUtil { - public static final String BITMAP_DECODING_EXCEPTION_MESSAGE = "Could not decode image data"; private DataSourceUtil() {} @@ -99,37 +90,4 @@ public final class DataSourceUtil { // Ignore. } } - - /** - * Decodes a {@link Bitmap} from a byte array using {@link BitmapFactory} and the {@link - * ExifInterface}. - */ - // BitmapFactory's options parameter is null-ok. - @SuppressWarnings("nullness:argument.type.incompatible") - public static Bitmap decode(byte[] data, int length, @Nullable BitmapFactory.Options options) - throws IOException { - @Nullable Bitmap bitmap = BitmapFactory.decodeByteArray(data, /* offset= */ 0, length, options); - checkArgument(bitmap != null, BITMAP_DECODING_EXCEPTION_MESSAGE); - // BitmapFactory doesn't read the exif header, so we use the ExifInterface to this do ensure the - // bitmap is correctly orientated. - ExifInterface exifInterface; - try (InputStream inputStream = new ByteArrayInputStream(data)) { - exifInterface = new ExifInterface(inputStream); - } - int rotationDegrees = exifInterface.getRotationDegrees(); - if (rotationDegrees != 0) { - Matrix matrix = new Matrix(); - matrix.postRotate(rotationDegrees); - bitmap = - Bitmap.createBitmap( - bitmap, - /* x= */ 0, - /* y= */ 0, - bitmap.getWidth(), - bitmap.getHeight(), - matrix, - /* filter= */ false); - } - return bitmap; - } } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/image/BitmapFactoryImageDecoder.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/image/BitmapFactoryImageDecoder.java index 89b7d2c936..50e7eaa581 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/image/BitmapFactoryImageDecoder.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/image/BitmapFactoryImageDecoder.java @@ -29,14 +29,14 @@ import androidx.annotation.VisibleForTesting; import androidx.media3.common.C; import androidx.media3.common.Format; import androidx.media3.common.MimeTypes; +import androidx.media3.common.ParserException; import androidx.media3.common.util.UnstableApi; -import androidx.media3.datasource.DataSourceUtil; +import androidx.media3.datasource.BitmapUtil; import androidx.media3.decoder.DecoderInputBuffer; import androidx.media3.decoder.SimpleDecoder; import androidx.media3.exoplayer.RendererCapabilities; import java.io.IOException; import java.nio.ByteBuffer; -import java.util.Objects; /** * An image decoder that uses {@link BitmapFactory} to decode images. @@ -159,20 +159,17 @@ public final class BitmapFactoryImageDecoder */ private static Bitmap decode(byte[] data, int length) throws ImageDecoderException { try { - return DataSourceUtil.decode(data, length, /* options= */ null); + return BitmapUtil.decode(data, length, /* options= */ null); + } catch (ParserException e) { + throw new ImageDecoderException( + "Could not decode image data with BitmapFactory. (data.length = " + + data.length + + ", input length = " + + length + + ")", + e); } catch (IOException e) { throw new ImageDecoderException(e); - } catch (IllegalArgumentException e) { - if (Objects.equals(e.getMessage(), DataSourceUtil.BITMAP_DECODING_EXCEPTION_MESSAGE)) { - throw new ImageDecoderException( - "Could not decode image data with BitmapFactory. (data.length = " - + data.length - + ", input length = " - + length - + ")"); - } else { - throw e; - } } } } diff --git a/libraries/session/src/test/java/androidx/media3/session/CacheBitmapLoaderTest.java b/libraries/session/src/test/java/androidx/media3/session/CacheBitmapLoaderTest.java index 4247c7d61a..676b2c71d5 100644 --- a/libraries/session/src/test/java/androidx/media3/session/CacheBitmapLoaderTest.java +++ b/libraries/session/src/test/java/androidx/media3/session/CacheBitmapLoaderTest.java @@ -25,6 +25,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.net.Uri; +import androidx.media3.common.ParserException; import androidx.media3.datasource.DataSourceBitmapLoader; import androidx.media3.datasource.HttpDataSource; import androidx.media3.test.utils.TestUtil; @@ -124,7 +125,7 @@ public class CacheBitmapLoaderTest { assertThat(future1).isSameInstanceAs(future2); ExecutionException executionException = assertThrows(ExecutionException.class, () -> future1.get(10, SECONDS)); - assertThat(executionException).hasCauseThat().isInstanceOf(IllegalArgumentException.class); + assertThat(executionException).hasCauseThat().isInstanceOf(ParserException.class); assertThat(executionException).hasMessageThat().contains("Could not decode image data"); }