Load bitmaps for MediaBrowserCompat.

* Transforms the `ListenableFuture<LibraryResult<MediaItem>>` and `ListenableFuture<LibraryResult<List<MediaItem>>>` to `ListenableFuture<MediaBrowserCompat.MediaItem>` and `ListenableFuture<List<MediaBrowserCompat.MediaItem>>`, and the result will be sent out when `ListenableFuture` the `MediaBrowserCompat.MediaItem` (or the list of it) is fulfilled.
* Add `artworkData` to the tests in `MediaBrowserCompatWithMediaLibraryServiceTest`.

PiperOrigin-RevId: 489205547
This commit is contained in:
tianyifeng 2022-11-17 15:23:35 +00:00 committed by microkatz
parent 6e73fc545d
commit 4ce171a3cf
5 changed files with 225 additions and 76 deletions

View File

@ -26,6 +26,7 @@ import static androidx.media3.session.LibraryResult.RESULT_SUCCESS;
import static androidx.media3.session.MediaUtils.TRANSACTION_SIZE_LIMIT_IN_BYTES; import static androidx.media3.session.MediaUtils.TRANSACTION_SIZE_LIMIT_IN_BYTES;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.os.BadParcelableException; import android.os.BadParcelableException;
import android.os.Bundle; import android.os.Bundle;
import android.os.RemoteException; import android.os.RemoteException;
@ -37,6 +38,7 @@ import androidx.core.util.ObjectsCompat;
import androidx.media.MediaBrowserServiceCompat; import androidx.media.MediaBrowserServiceCompat;
import androidx.media.MediaSessionManager.RemoteUserInfo; import androidx.media.MediaSessionManager.RemoteUserInfo;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.util.ConditionVariable; import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.Log; import androidx.media3.common.util.Log;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
@ -44,14 +46,19 @@ import androidx.media3.session.MediaLibraryService.LibraryParams;
import androidx.media3.session.MediaSession.ControllerCb; import androidx.media3.session.MediaSession.ControllerCb;
import androidx.media3.session.MediaSession.ControllerInfo; import androidx.media3.session.MediaSession.ControllerInfo;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import org.checkerframework.checker.nullness.compatqual.NullableType;
/** /**
* Implementation of {@link MediaBrowserServiceCompat} for interoperability between {@link * Implementation of {@link MediaBrowserServiceCompat} for interoperability between {@link
@ -218,7 +225,11 @@ import java.util.concurrent.atomic.AtomicReference;
ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> future = ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> future =
librarySessionImpl.onGetChildrenOnHandler( librarySessionImpl.onGetChildrenOnHandler(
controller, parentId, page, pageSize, params); controller, parentId, page, pageSize, params);
sendLibraryResultWithMediaItemsWhenReady(result, future); ListenableFuture<@NullableType List<MediaBrowserCompat.MediaItem>>
browserItemsFuture =
Util.transformFutureAsync(
future, createMediaItemsToBrowserItemsAsyncFunction());
sendLibraryResultWithMediaItemsWhenReady(result, browserItemsFuture);
return; return;
} }
// Cannot distinguish onLoadChildren() why it's called either by // Cannot distinguish onLoadChildren() why it's called either by
@ -236,7 +247,9 @@ import java.util.concurrent.atomic.AtomicReference;
/* page= */ 0, /* page= */ 0,
/* pageSize= */ Integer.MAX_VALUE, /* pageSize= */ Integer.MAX_VALUE,
/* params= */ null); /* params= */ null);
sendLibraryResultWithMediaItemsWhenReady(result, future); ListenableFuture<@NullableType List<MediaBrowserCompat.MediaItem>> browserItemsFuture =
Util.transformFutureAsync(future, createMediaItemsToBrowserItemsAsyncFunction());
sendLibraryResultWithMediaItemsWhenReady(result, browserItemsFuture);
}); });
} }
@ -264,7 +277,9 @@ import java.util.concurrent.atomic.AtomicReference;
} }
ListenableFuture<LibraryResult<MediaItem>> future = ListenableFuture<LibraryResult<MediaItem>> future =
librarySessionImpl.onGetItemOnHandler(controller, itemId); librarySessionImpl.onGetItemOnHandler(controller, itemId);
sendLibraryResultWithMediaItemWhenReady(result, future); ListenableFuture<MediaBrowserCompat.@NullableType MediaItem> browserItemFuture =
Util.transformFutureAsync(future, createMediaItemToBrowserItemAsyncFunction());
sendLibraryResultWithMediaItemWhenReady(result, browserItemFuture);
}); });
} }
@ -362,17 +377,12 @@ import java.util.concurrent.atomic.AtomicReference;
private static void sendLibraryResultWithMediaItemWhenReady( private static void sendLibraryResultWithMediaItemWhenReady(
Result<MediaBrowserCompat.MediaItem> result, Result<MediaBrowserCompat.MediaItem> result,
ListenableFuture<LibraryResult<MediaItem>> future) { ListenableFuture<MediaBrowserCompat.@NullableType MediaItem> future) {
future.addListener( future.addListener(
() -> { () -> {
try { try {
LibraryResult<MediaItem> libraryResult = MediaBrowserCompat.MediaItem mediaItem = future.get();
checkNotNull(future.get(), "LibraryResult must not be null"); result.sendResult(mediaItem);
if (libraryResult.resultCode != RESULT_SUCCESS || libraryResult.value == null) {
result.sendResult(/* result= */ null);
} else {
result.sendResult(MediaUtils.convertToBrowserItem(libraryResult.value));
}
} catch (CancellationException | ExecutionException | InterruptedException unused) { } catch (CancellationException | ExecutionException | InterruptedException unused) {
result.sendError(/* extras= */ null); result.sendError(/* extras= */ null);
} }
@ -382,20 +392,15 @@ import java.util.concurrent.atomic.AtomicReference;
private static void sendLibraryResultWithMediaItemsWhenReady( private static void sendLibraryResultWithMediaItemsWhenReady(
Result<List<MediaBrowserCompat.MediaItem>> result, Result<List<MediaBrowserCompat.MediaItem>> result,
ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> future) { ListenableFuture<@NullableType List<MediaBrowserCompat.MediaItem>> future) {
future.addListener( future.addListener(
() -> { () -> {
try { try {
LibraryResult<ImmutableList<MediaItem>> libraryResult = List<MediaBrowserCompat.MediaItem> mediaItems = future.get();
checkNotNull(future.get(), "LibraryResult must not be null");
if (libraryResult.resultCode != RESULT_SUCCESS || libraryResult.value == null) {
result.sendResult(/* result= */ null);
} else {
result.sendResult( result.sendResult(
MediaUtils.truncateListBySize( (mediaItems == null)
MediaUtils.convertToBrowserItemList(libraryResult.value), ? null
TRANSACTION_SIZE_LIMIT_IN_BYTES)); : MediaUtils.truncateListBySize(mediaItems, TRANSACTION_SIZE_LIMIT_IN_BYTES));
}
} catch (CancellationException | ExecutionException | InterruptedException unused) { } catch (CancellationException | ExecutionException | InterruptedException unused) {
result.sendError(/* extras= */ null); result.sendError(/* extras= */ null);
} }
@ -403,6 +408,130 @@ import java.util.concurrent.atomic.AtomicReference;
MoreExecutors.directExecutor()); MoreExecutors.directExecutor());
} }
private AsyncFunction<
LibraryResult<ImmutableList<MediaItem>>, @NullableType List<MediaBrowserCompat.MediaItem>>
createMediaItemsToBrowserItemsAsyncFunction() {
return result -> {
checkNotNull(result, "LibraryResult must not be null");
SettableFuture<@NullableType List<MediaBrowserCompat.MediaItem>> outputFuture =
SettableFuture.create();
if (result.resultCode != RESULT_SUCCESS || result.value == null) {
outputFuture.set(null);
return outputFuture;
}
ImmutableList<MediaItem> mediaItems = result.value;
if (mediaItems.isEmpty()) {
outputFuture.set(new ArrayList<>());
return outputFuture;
}
List<@NullableType ListenableFuture<Bitmap>> bitmapFutures = new ArrayList<>();
outputFuture.addListener(
() -> {
if (outputFuture.isCancelled()) {
cancelAllFutures(bitmapFutures);
}
},
MoreExecutors.directExecutor());
final AtomicInteger resultCount = new AtomicInteger(0);
Runnable handleBitmapFuturesTask =
() -> {
int completedBitmapFutureCount = resultCount.incrementAndGet();
if (completedBitmapFutureCount == mediaItems.size()) {
handleBitmapFuturesAllCompletedAndSetOutputFuture(
bitmapFutures, mediaItems, outputFuture);
}
};
for (int i = 0; i < mediaItems.size(); i++) {
MediaItem mediaItem = mediaItems.get(i);
MediaMetadata metadata = mediaItem.mediaMetadata;
if (metadata.artworkData == null) {
bitmapFutures.add(null);
handleBitmapFuturesTask.run();
} else {
ListenableFuture<Bitmap> bitmapFuture =
librarySessionImpl.getBitmapLoader().decodeBitmap(metadata.artworkData);
bitmapFutures.add(bitmapFuture);
bitmapFuture.addListener(handleBitmapFuturesTask, MoreExecutors.directExecutor());
}
}
return outputFuture;
};
}
private void handleBitmapFuturesAllCompletedAndSetOutputFuture(
List<@NullableType ListenableFuture<Bitmap>> bitmapFutures,
List<MediaItem> mediaItems,
SettableFuture<@NullableType List<MediaBrowserCompat.MediaItem>> outputFuture) {
List<MediaBrowserCompat.MediaItem> outputMediaItems = new ArrayList<>();
for (int i = 0; i < bitmapFutures.size(); i++) {
@Nullable ListenableFuture<Bitmap> future = bitmapFutures.get(i);
@Nullable Bitmap bitmap = null;
if (future != null) {
try {
bitmap = Futures.getDone(future);
} catch (CancellationException | ExecutionException e) {
Log.d(TAG, "Failed to get bitmap");
}
}
outputMediaItems.add(MediaUtils.convertToBrowserItem(mediaItems.get(i), bitmap));
}
outputFuture.set(outputMediaItems);
}
private static <T> void cancelAllFutures(List<@NullableType ListenableFuture<T>> futures) {
for (int i = 0; i < futures.size(); i++) {
if (futures.get(i) != null) {
futures.get(i).cancel(/* mayInterruptIfRunning= */ false);
}
}
}
private AsyncFunction<LibraryResult<MediaItem>, MediaBrowserCompat.@NullableType MediaItem>
createMediaItemToBrowserItemAsyncFunction() {
return result -> {
checkNotNull(result, "LibraryResult must not be null");
SettableFuture<MediaBrowserCompat.@NullableType MediaItem> outputFuture =
SettableFuture.create();
if (result.resultCode != RESULT_SUCCESS || result.value == null) {
outputFuture.set(null);
return outputFuture;
}
MediaItem mediaItem = result.value;
MediaMetadata metadata = mediaItem.mediaMetadata;
if (metadata.artworkData == null) {
outputFuture.set(MediaUtils.convertToBrowserItem(mediaItem, /* artworkBitmap= */ null));
return outputFuture;
}
ListenableFuture<Bitmap> bitmapFuture =
librarySessionImpl.getBitmapLoader().decodeBitmap(metadata.artworkData);
outputFuture.addListener(
() -> {
if (outputFuture.isCancelled()) {
bitmapFuture.cancel(/* mayInterruptIfRunning= */ false);
}
},
MoreExecutors.directExecutor());
bitmapFuture.addListener(
() -> {
@Nullable Bitmap bitmap = null;
try {
bitmap = Futures.getDone(bitmapFuture);
} catch (CancellationException | ExecutionException e) {
Log.d(TAG, "failed to get bitmap");
}
outputFuture.set(MediaUtils.convertToBrowserItem(mediaItem, bitmap));
},
MoreExecutors.directExecutor());
return outputFuture;
};
}
private static <T> void ignoreFuture(Future<T> unused) { private static <T> void ignoreFuture(Future<T> unused) {
// no-op // no-op
} }
@ -504,7 +633,9 @@ import java.util.concurrent.atomic.AtomicReference;
ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> future = ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> future =
librarySessionImpl.onGetSearchResultOnHandler( librarySessionImpl.onGetSearchResultOnHandler(
request.controller, request.query, page, pageSize, libraryParams); request.controller, request.query, page, pageSize, libraryParams);
sendLibraryResultWithMediaItemsWhenReady(request.result, future); ListenableFuture<@NullableType List<MediaBrowserCompat.MediaItem>> mediaItemsFuture =
Util.transformFutureAsync(future, createMediaItemsToBrowserItemsAsyncFunction());
sendLibraryResultWithMediaItemsWhenReady(request.result, mediaItemsFuture);
} }
}); });
} }

View File

@ -136,9 +136,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
errorMessage, /* cause= */ null, PlaybackException.ERROR_CODE_REMOTE_ERROR); errorMessage, /* cause= */ null, PlaybackException.ERROR_CODE_REMOTE_ERROR);
} }
/** Converts a {@link MediaItem} to a {@link MediaBrowserCompat.MediaItem}. */ public static MediaBrowserCompat.MediaItem convertToBrowserItem(
public static MediaBrowserCompat.MediaItem convertToBrowserItem(MediaItem item) { MediaItem item, @Nullable Bitmap artworkBitmap) {
MediaDescriptionCompat description = convertToMediaDescriptionCompat(item); MediaDescriptionCompat description = convertToMediaDescriptionCompat(item, artworkBitmap);
MediaMetadata metadata = item.mediaMetadata; MediaMetadata metadata = item.mediaMetadata;
int flags = 0; int flags = 0;
if (metadata.folderType != null && metadata.folderType != MediaMetadata.FOLDER_TYPE_NONE) { if (metadata.folderType != null && metadata.folderType != MediaMetadata.FOLDER_TYPE_NONE) {
@ -150,15 +150,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
return new MediaBrowserCompat.MediaItem(description, flags); return new MediaBrowserCompat.MediaItem(description, flags);
} }
/** Converts a list of {@link MediaItem} to a list of {@link MediaBrowserCompat.MediaItem}. */
public static List<MediaBrowserCompat.MediaItem> convertToBrowserItemList(List<MediaItem> items) {
List<MediaBrowserCompat.MediaItem> result = new ArrayList<>();
for (int i = 0; i < items.size(); i++) {
result.add(convertToBrowserItem(items.get(i)));
}
return result;
}
/** Converts a {@link MediaBrowserCompat.MediaItem} to a {@link MediaItem}. */ /** Converts a {@link MediaBrowserCompat.MediaItem} to a {@link MediaItem}. */
public static MediaItem convertToMediaItem(MediaBrowserCompat.MediaItem item) { public static MediaItem convertToMediaItem(MediaBrowserCompat.MediaItem item) {
return convertToMediaItem(item.getDescription(), item.isBrowsable(), item.isPlayable()); return convertToMediaItem(item.getDescription(), item.isBrowsable(), item.isPlayable());
@ -320,16 +311,32 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
return result; return result;
} }
/** Converts a {@link MediaItem} to a {@link MediaDescriptionCompat}. */ /**
* Converts a {@link MediaItem} to a {@link MediaDescriptionCompat}.
*
* @deprecated Use {@link #convertToMediaDescriptionCompat(MediaItem, Bitmap)} instead.
*/
@Deprecated
public static MediaDescriptionCompat convertToMediaDescriptionCompat(MediaItem item) { public static MediaDescriptionCompat convertToMediaDescriptionCompat(MediaItem item) {
MediaMetadata metadata = item.mediaMetadata;
@Nullable Bitmap artworkBitmap = null;
if (metadata.artworkData != null) {
artworkBitmap =
BitmapFactory.decodeByteArray(metadata.artworkData, 0, metadata.artworkData.length);
}
return convertToMediaDescriptionCompat(item, artworkBitmap);
}
/** Converts a {@link MediaItem} to a {@link MediaDescriptionCompat} */
public static MediaDescriptionCompat convertToMediaDescriptionCompat(
MediaItem item, @Nullable Bitmap artworkBitmap) {
MediaDescriptionCompat.Builder builder = MediaDescriptionCompat.Builder builder =
new MediaDescriptionCompat.Builder() new MediaDescriptionCompat.Builder()
.setMediaId(item.mediaId.equals(MediaItem.DEFAULT_MEDIA_ID) ? null : item.mediaId); .setMediaId(item.mediaId.equals(MediaItem.DEFAULT_MEDIA_ID) ? null : item.mediaId);
MediaMetadata metadata = item.mediaMetadata; MediaMetadata metadata = item.mediaMetadata;
if (metadata.artworkData != null) { if (artworkBitmap != null) {
Bitmap artwork = builder.setIconBitmap(artworkBitmap);
BitmapFactory.decodeByteArray(metadata.artworkData, 0, metadata.artworkData.length);
builder.setIconBitmap(artwork);
} }
@Nullable Bundle extras = metadata.extras; @Nullable Bundle extras = metadata.extras;
if (metadata.folderType != null && metadata.folderType != MediaMetadata.FOLDER_TYPE_NONE) { if (metadata.folderType != null && metadata.folderType != MediaMetadata.FOLDER_TYPE_NONE) {

View File

@ -129,6 +129,7 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(itemRef.get().getMediaId()).isEqualTo(mediaId); assertThat(itemRef.get().getMediaId()).isEqualTo(mediaId);
assertThat(itemRef.get().isBrowsable()).isTrue(); assertThat(itemRef.get().isBrowsable()).isTrue();
assertThat(itemRef.get().getDescription().getIconBitmap()).isNotNull();
} }
@Test @Test
@ -151,6 +152,7 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(itemRef.get().getMediaId()).isEqualTo(mediaId); assertThat(itemRef.get().getMediaId()).isEqualTo(mediaId);
assertThat(itemRef.get().isPlayable()).isTrue(); assertThat(itemRef.get().isPlayable()).isTrue();
assertThat(itemRef.get().getDescription().getIconBitmap()).isNotNull();
} }
@Test @Test
@ -181,6 +183,7 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest
BundleSubject.assertThat(description.getExtras()) BundleSubject.assertThat(description.getExtras())
.string(METADATA_EXTRA_KEY) .string(METADATA_EXTRA_KEY)
.isEqualTo(METADATA_EXTRA_VALUE); .isEqualTo(METADATA_EXTRA_VALUE);
assertThat(description.getIconBitmap()).isNotNull();
} }
@Test @Test
@ -245,6 +248,7 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest
EXTRAS_KEY_COMPLETION_STATUS, EXTRAS_KEY_COMPLETION_STATUS,
/* defaultValue= */ EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED + 1)) /* defaultValue= */ EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED + 1))
.isEqualTo(EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED); .isEqualTo(EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED);
assertThat(mediaItem.getDescription().getIconBitmap()).isNotNull();
} }
} }
@ -311,6 +315,7 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest
int relativeIndex = originalIndex - fromIndex; int relativeIndex = originalIndex - fromIndex;
assertThat(children.get(relativeIndex).getMediaId()) assertThat(children.get(relativeIndex).getMediaId())
.isEqualTo(GET_CHILDREN_RESULT.get(originalIndex)); .isEqualTo(GET_CHILDREN_RESULT.get(originalIndex));
assertThat(children.get(relativeIndex).getDescription().getIconBitmap()).isNotNull();
} }
latch.countDown(); latch.countDown();
} }

View File

@ -32,7 +32,6 @@ import android.support.v4.media.RatingCompat;
import android.support.v4.media.session.MediaControllerCompat; import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat; import android.support.v4.media.session.PlaybackStateCompat;
import android.text.TextUtils;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media.AudioAttributesCompat; import androidx.media.AudioAttributesCompat;
import androidx.media3.common.AudioAttributes; import androidx.media3.common.AudioAttributes;
@ -71,23 +70,6 @@ public final class MediaUtilsTest {
bitmapLoader = new CacheBitmapLoader(new SimpleBitmapLoader()); bitmapLoader = new CacheBitmapLoader(new SimpleBitmapLoader());
} }
@Test
public void convertToBrowserItem() {
String mediaId = "testId";
CharSequence trackTitle = "testTitle";
MediaItem mediaItem =
new MediaItem.Builder()
.setMediaId(mediaId)
.setMediaMetadata(new MediaMetadata.Builder().setTitle(trackTitle).build())
.build();
MediaBrowserCompat.MediaItem browserItem = MediaUtils.convertToBrowserItem(mediaItem);
assertThat(browserItem.getDescription()).isNotNull();
assertThat(browserItem.getDescription().getMediaId()).isEqualTo(mediaId);
assertThat(TextUtils.equals(browserItem.getDescription().getTitle(), trackTitle)).isTrue();
}
@Test @Test
public void convertToMediaItem_browserItemToMediaItem() { public void convertToMediaItem_browserItemToMediaItem() {
String mediaId = "testId"; String mediaId = "testId";
@ -115,18 +97,6 @@ public final class MediaUtilsTest {
assertThat(mediaItem.mediaMetadata.title.toString()).isEqualTo(title); assertThat(mediaItem.mediaMetadata.title.toString()).isEqualTo(title);
} }
@Test
public void convertToBrowserItemList() {
int size = 3;
List<MediaItem> mediaItems = MediaTestUtils.createMediaItems(size);
List<MediaBrowserCompat.MediaItem> browserItems =
MediaUtils.convertToBrowserItemList(mediaItems);
assertThat(browserItems).hasSize(size);
for (int i = 0; i < size; ++i) {
assertThat(browserItems.get(i).getMediaId()).isEqualTo(mediaItems.get(i).mediaId);
}
}
@Test @Test
public void convertBrowserItemListToMediaItemList() { public void convertBrowserItemListToMediaItemList() {
int size = 3; int size = 3;

View File

@ -51,6 +51,7 @@ import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIB
import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE; import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE;
import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID; import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ONE_WITH_NON_SUBSCRIBED_ID;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.junit.Assert.fail;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
@ -72,6 +73,7 @@ import androidx.media3.test.session.common.TestUtils;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -92,6 +94,8 @@ public class MockMediaLibraryService extends MediaLibraryService {
public static final String CONNECTION_HINTS_KEY_REMOVE_COMMAND_CODE_LIBRARY_SEARCH = public static final String CONNECTION_HINTS_KEY_REMOVE_COMMAND_CODE_LIBRARY_SEARCH =
"CONNECTION_HINTS_KEY_REMOVE_SEARCH_SESSION_COMMAND"; "CONNECTION_HINTS_KEY_REMOVE_SEARCH_SESSION_COMMAND";
private static final String TEST_IMAGE_PATH = "media/png/non-motion-photo-shortened.png";
public static final MediaItem ROOT_ITEM = public static final MediaItem ROOT_ITEM =
new MediaItem.Builder() new MediaItem.Builder()
.setMediaId(ROOT_ID) .setMediaId(ROOT_ID)
@ -115,6 +119,8 @@ public class MockMediaLibraryService extends MediaLibraryService {
@Nullable @Nullable
private static LibraryParams expectedParams; private static LibraryParams expectedParams;
@Nullable private static byte[] testArtworkData;
MediaLibrarySession session; MediaLibrarySession session;
TestHandler handler; TestHandler handler;
HandlerThread handlerThread; HandlerThread handlerThread;
@ -238,7 +244,8 @@ public class MockMediaLibraryService extends MediaLibraryService {
LibraryResult.ofItem(createBrowsableMediaItem(mediaId), /* params= */ null)); LibraryResult.ofItem(createBrowsableMediaItem(mediaId), /* params= */ null));
case MEDIA_ID_GET_PLAYABLE_ITEM: case MEDIA_ID_GET_PLAYABLE_ITEM:
return Futures.immediateFuture( return Futures.immediateFuture(
LibraryResult.ofItem(createPlayableMediaItem(mediaId), /* params= */ null)); LibraryResult.ofItem(
createPlayableMediaItemWithArtworkData(mediaId), /* params= */ null));
case MEDIA_ID_GET_ITEM_WITH_METADATA: case MEDIA_ID_GET_ITEM_WITH_METADATA:
return Futures.immediateFuture( return Futures.immediateFuture(
LibraryResult.ofItem(createMediaItemWithMetadata(mediaId), /* params= */ null)); LibraryResult.ofItem(createMediaItemWithMetadata(mediaId), /* params= */ null));
@ -445,20 +452,32 @@ public class MockMediaLibraryService extends MediaLibraryService {
// Create a list of MediaItem from the list of media IDs. // Create a list of MediaItem from the list of media IDs.
List<MediaItem> result = new ArrayList<>(); List<MediaItem> result = new ArrayList<>();
for (int i = 0; i < paginatedMediaIdList.size(); i++) { for (int i = 0; i < paginatedMediaIdList.size(); i++) {
result.add(createPlayableMediaItem(paginatedMediaIdList.get(i))); result.add(createPlayableMediaItemWithArtworkData(paginatedMediaIdList.get(i)));
} }
return result; return result;
} }
private static MediaItem createBrowsableMediaItem(String mediaId) { private MediaItem createBrowsableMediaItem(String mediaId) {
MediaMetadata mediaMetadata = MediaMetadata mediaMetadata =
new MediaMetadata.Builder() new MediaMetadata.Builder()
.setFolderType(MediaMetadata.FOLDER_TYPE_MIXED) .setFolderType(MediaMetadata.FOLDER_TYPE_MIXED)
.setIsPlayable(false) .setIsPlayable(false)
.setArtworkData(getArtworkData(), MediaMetadata.PICTURE_TYPE_FRONT_COVER)
.build(); .build();
return new MediaItem.Builder().setMediaId(mediaId).setMediaMetadata(mediaMetadata).build(); return new MediaItem.Builder().setMediaId(mediaId).setMediaMetadata(mediaMetadata).build();
} }
private MediaItem createPlayableMediaItemWithArtworkData(String mediaId) {
MediaItem mediaItem = createPlayableMediaItem(mediaId);
MediaMetadata mediaMetadataWithArtwork =
mediaItem
.mediaMetadata
.buildUpon()
.setArtworkData(getArtworkData(), MediaMetadata.PICTURE_TYPE_FRONT_COVER)
.build();
return mediaItem.buildUpon().setMediaMetadata(mediaMetadataWithArtwork).build();
}
private static MediaItem createPlayableMediaItem(String mediaId) { private static MediaItem createPlayableMediaItem(String mediaId) {
Bundle extras = new Bundle(); Bundle extras = new Bundle();
extras.putInt(EXTRAS_KEY_COMPLETION_STATUS, EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED); extras.putInt(EXTRAS_KEY_COMPLETION_STATUS, EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED);
@ -471,15 +490,32 @@ public class MockMediaLibraryService extends MediaLibraryService {
return new MediaItem.Builder().setMediaId(mediaId).setMediaMetadata(mediaMetadata).build(); return new MediaItem.Builder().setMediaId(mediaId).setMediaMetadata(mediaMetadata).build();
} }
private static MediaItem createMediaItemWithMetadata(String mediaId) { private MediaItem createMediaItemWithMetadata(String mediaId) {
MediaMetadata mediaMetadata = MediaTestUtils.createMediaMetadata(); MediaMetadata mediaMetadataWithArtwork =
MediaTestUtils.createMediaMetadata()
.buildUpon()
.setArtworkData(getArtworkData(), MediaMetadata.PICTURE_TYPE_FRONT_COVER)
.build();
return new MediaItem.Builder() return new MediaItem.Builder()
.setMediaId(mediaId) .setMediaId(mediaId)
.setRequestMetadata( .setRequestMetadata(
new MediaItem.RequestMetadata.Builder() new MediaItem.RequestMetadata.Builder()
.setMediaUri(CommonConstants.METADATA_MEDIA_URI) .setMediaUri(CommonConstants.METADATA_MEDIA_URI)
.build()) .build())
.setMediaMetadata(mediaMetadata) .setMediaMetadata(mediaMetadataWithArtwork)
.build(); .build();
} }
private byte[] getArtworkData() {
if (testArtworkData != null) {
return testArtworkData;
}
try {
testArtworkData =
TestUtils.getByteArrayForScaledBitmap(getApplicationContext(), TEST_IMAGE_PATH);
} catch (IOException e) {
fail(e.getMessage());
}
return testArtworkData;
}
} }