Move setting bitmapFactory options from interface to implementation

Moves setting bitmapFactory options from BitmapLoader to DatasourceBitmapLoader

BitmapLoader is a general interface for bitmap loading that could use loading implementations other that BitmapFactory, with the rise of Glide being a loader of choice. It's best to correct this interface so that it remains generic

We can't deprecate easily because the other loadBitmap method in that case has a default implementation that relies on the first one, so the change is still breaking. BitmapLoader is public api in common, but it's @UnstableAPI and hasn't been around for very long (be38670391 added it), so it seems this is the best way forward.

PiperOrigin-RevId: 597897098
This commit is contained in:
tofunmi 2024-01-12 11:18:10 -08:00 committed by Copybara-Service
parent 09a547953b
commit 6879698d7e
6 changed files with 41 additions and 33 deletions

View File

@ -16,7 +16,6 @@
package androidx.media3.common.util; package androidx.media3.common.util;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri; import android.net.Uri;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.MediaMetadata; import androidx.media3.common.MediaMetadata;
@ -29,12 +28,7 @@ public interface BitmapLoader {
ListenableFuture<Bitmap> decodeBitmap(byte[] data); ListenableFuture<Bitmap> decodeBitmap(byte[] data);
/** Loads an image from {@code uri}. */ /** Loads an image from {@code uri}. */
default ListenableFuture<Bitmap> loadBitmap(Uri uri) { ListenableFuture<Bitmap> loadBitmap(Uri uri);
return loadBitmap(uri, /* options= */ null);
}
/** Loads an image from {@code uri} with the given {@link BitmapFactory.Options}. */
ListenableFuture<Bitmap> loadBitmap(Uri uri, @Nullable BitmapFactory.Options options);
/** /**
* Loads an image from {@link MediaMetadata}. Returns null if {@code metadata} doesn't contain * Loads an image from {@link MediaMetadata}. Returns null if {@code metadata} doesn't contain

View File

@ -205,13 +205,13 @@ public class DataSourceBitmapLoaderTest {
File file = tempFolder.newFile(); File file = tempFolder.newFile();
Files.write(Paths.get(file.getAbsolutePath()), imageData); Files.write(Paths.get(file.getAbsolutePath()), imageData);
Uri uri = Uri.fromFile(file); Uri uri = Uri.fromFile(file);
DataSourceBitmapLoader bitmapLoader =
new DataSourceBitmapLoader(MoreExecutors.newDirectExecutorService(), dataSourceFactory);
BitmapFactory.Options options = new BitmapFactory.Options(); BitmapFactory.Options options = new BitmapFactory.Options();
options.inMutable = true; options.inMutable = true;
DataSourceBitmapLoader bitmapLoader =
new DataSourceBitmapLoader(
MoreExecutors.newDirectExecutorService(), dataSourceFactory, options);
Bitmap bitmap = bitmapLoader.loadBitmap(uri, options).get(); Bitmap bitmap = bitmapLoader.loadBitmap(uri).get();
assertThat(bitmap.isMutable()).isTrue(); assertThat(bitmap.isMutable()).isTrue();
} }

View File

@ -39,7 +39,7 @@ import java.util.concurrent.Executors;
/** /**
* A {@link BitmapLoader} implementation that uses a {@link DataSource} to support fetching images * A {@link BitmapLoader} implementation that uses a {@link DataSource} to support fetching images
* from URIs. * from URIs and {@link BitmapFactory} to load them into {@link Bitmap}.
* *
* <p>Loading tasks are delegated to a {@link ListeningExecutorService} defined during construction. * <p>Loading tasks are delegated to a {@link ListeningExecutorService} defined during construction.
* If no executor service is passed, all tasks are delegated to a single-thread executor service * If no executor service is passed, all tasks are delegated to a single-thread executor service
@ -54,6 +54,7 @@ public final class DataSourceBitmapLoader implements BitmapLoader {
private final ListeningExecutorService listeningExecutorService; private final ListeningExecutorService listeningExecutorService;
private final DataSource.Factory dataSourceFactory; private final DataSource.Factory dataSourceFactory;
@Nullable private final BitmapFactory.Options options;
/** /**
* Creates an instance that uses a {@link DefaultHttpDataSource} for image loading and delegates * Creates an instance that uses a {@link DefaultHttpDataSource} for image loading and delegates
@ -72,17 +73,33 @@ public final class DataSourceBitmapLoader implements BitmapLoader {
*/ */
public DataSourceBitmapLoader( public DataSourceBitmapLoader(
ListeningExecutorService listeningExecutorService, DataSource.Factory dataSourceFactory) { ListeningExecutorService listeningExecutorService, DataSource.Factory dataSourceFactory) {
this(listeningExecutorService, dataSourceFactory, /* options= */ null);
}
/**
* Creates an instance that delegates loading tasks to the {@link ListeningExecutorService}.
*
* @param listeningExecutorService The {@link ListeningExecutorService}.
* @param dataSourceFactory The {@link DataSource.Factory} that creates the {@link DataSource}
* used to load the image.
* @param options The {@link BitmapFactory.Options} the image should be loaded with.
*/
public DataSourceBitmapLoader(
ListeningExecutorService listeningExecutorService,
DataSource.Factory dataSourceFactory,
@Nullable BitmapFactory.Options options) {
this.listeningExecutorService = listeningExecutorService; this.listeningExecutorService = listeningExecutorService;
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.options = options;
} }
@Override @Override
public ListenableFuture<Bitmap> decodeBitmap(byte[] data) { public ListenableFuture<Bitmap> decodeBitmap(byte[] data) {
return listeningExecutorService.submit(() -> decode(data, /* options= */ null)); return listeningExecutorService.submit(() -> decode(data, options));
} }
@Override @Override
public ListenableFuture<Bitmap> loadBitmap(Uri uri, @Nullable BitmapFactory.Options options) { public ListenableFuture<Bitmap> loadBitmap(Uri uri) {
return listeningExecutorService.submit( return listeningExecutorService.submit(
() -> load(dataSourceFactory.createDataSource(), uri, options)); () -> load(dataSourceFactory.createDataSource(), uri, options));
} }

View File

@ -18,7 +18,6 @@ package androidx.media3.session;
import static androidx.media3.common.util.Assertions.checkStateNotNull; import static androidx.media3.common.util.Assertions.checkStateNotNull;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri; import android.net.Uri;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.util.BitmapLoader; import androidx.media3.common.util.BitmapLoader;
@ -60,11 +59,11 @@ public final class CacheBitmapLoader implements BitmapLoader {
} }
@Override @Override
public ListenableFuture<Bitmap> loadBitmap(Uri uri, @Nullable BitmapFactory.Options options) { public ListenableFuture<Bitmap> loadBitmap(Uri uri) {
if (lastBitmapLoadRequest != null && lastBitmapLoadRequest.matches(uri)) { if (lastBitmapLoadRequest != null && lastBitmapLoadRequest.matches(uri)) {
return lastBitmapLoadRequest.getFuture(); return lastBitmapLoadRequest.getFuture();
} }
ListenableFuture<Bitmap> future = bitmapLoader.loadBitmap(uri, options); ListenableFuture<Bitmap> future = bitmapLoader.loadBitmap(uri);
lastBitmapLoadRequest = new BitmapLoadRequest(uri, future); lastBitmapLoadRequest = new BitmapLoadRequest(uri, future);
return future; return future;
} }

View File

@ -68,24 +68,21 @@ public final class SimpleBitmapLoader implements BitmapLoader {
@Override @Override
public ListenableFuture<Bitmap> decodeBitmap(byte[] data) { public ListenableFuture<Bitmap> decodeBitmap(byte[] data) {
return executorService.submit(() -> decode(data, /* options= */ null)); return executorService.submit(() -> decode(data));
} }
@Override @Override
public ListenableFuture<Bitmap> loadBitmap(Uri uri, @Nullable BitmapFactory.Options options) { public ListenableFuture<Bitmap> loadBitmap(Uri uri) {
return executorService.submit(() -> load(uri, options)); return executorService.submit(() -> load(uri));
} }
// BitmapFactory's options parameter is null-ok. private static Bitmap decode(byte[] data) {
@SuppressWarnings("nullness:argument.type.incompatible") @Nullable Bitmap bitmap = BitmapFactory.decodeByteArray(data, /* offset= */ 0, data.length);
private static Bitmap decode(byte[] data, @Nullable BitmapFactory.Options options) {
@Nullable
Bitmap bitmap = BitmapFactory.decodeByteArray(data, /* offset= */ 0, data.length, options);
checkArgument(bitmap != null, "Could not decode image data"); checkArgument(bitmap != null, "Could not decode image data");
return bitmap; return bitmap;
} }
private static Bitmap load(Uri uri, @Nullable BitmapFactory.Options options) throws IOException { private static Bitmap load(Uri uri) throws IOException {
if ("file".equals(uri.getScheme())) { if ("file".equals(uri.getScheme())) {
@Nullable String path = uri.getPath(); @Nullable String path = uri.getPath();
if (path == null) { if (path == null) {
@ -108,7 +105,7 @@ public final class SimpleBitmapLoader implements BitmapLoader {
throw new IOException("Invalid response status code: " + responseCode); throw new IOException("Invalid response status code: " + responseCode);
} }
try (InputStream inputStream = httpConnection.getInputStream()) { try (InputStream inputStream = httpConnection.getInputStream()) {
return decode(ByteStreams.toByteArray(inputStream), options); return decode(ByteStreams.toByteArray(inputStream));
} }
} }
} }

View File

@ -112,17 +112,18 @@ public final class ImageAssetLoader implements AssetLoader {
progressState = PROGRESS_STATE_AVAILABLE; progressState = PROGRESS_STATE_AVAILABLE;
listener.onDurationUs(editedMediaItem.durationUs); listener.onDurationUs(editedMediaItem.durationUs);
listener.onTrackCount(1); listener.onTrackCount(1);
BitmapLoader bitmapLoader =
new DataSourceBitmapLoader(
MoreExecutors.listeningDecorator(scheduledExecutorService), dataSourceFactory);
MediaItem.LocalConfiguration localConfiguration =
checkNotNull(editedMediaItem.mediaItem.localConfiguration);
@Nullable BitmapFactory.Options options = null; @Nullable BitmapFactory.Options options = null;
if (Util.SDK_INT >= 26) { if (Util.SDK_INT >= 26) {
options = new BitmapFactory.Options(); options = new BitmapFactory.Options();
options.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB); options.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
} }
ListenableFuture<Bitmap> future = bitmapLoader.loadBitmap(localConfiguration.uri, options); BitmapLoader bitmapLoader =
new DataSourceBitmapLoader(
MoreExecutors.listeningDecorator(scheduledExecutorService), dataSourceFactory, options);
MediaItem.LocalConfiguration localConfiguration =
checkNotNull(editedMediaItem.mediaItem.localConfiguration);
ListenableFuture<Bitmap> future = bitmapLoader.loadBitmap(localConfiguration.uri);
Futures.addCallback( Futures.addCallback(
future, future,
new FutureCallback<Bitmap>() { new FutureCallback<Bitmap>() {