diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaLibraryServiceLegacyStub.java b/libraries/session/src/main/java/androidx/media3/session/MediaLibraryServiceLegacyStub.java index eeb4f3674a..60de48cae1 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaLibraryServiceLegacyStub.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaLibraryServiceLegacyStub.java @@ -26,6 +26,7 @@ import static androidx.media3.session.LibraryResult.RESULT_SUCCESS; import static androidx.media3.session.MediaUtils.TRANSACTION_SIZE_LIMIT_IN_BYTES; import android.annotation.SuppressLint; +import android.graphics.Bitmap; import android.os.BadParcelableException; import android.os.Bundle; import android.os.RemoteException; @@ -37,6 +38,7 @@ import androidx.core.util.ObjectsCompat; import androidx.media.MediaBrowserServiceCompat; import androidx.media.MediaSessionManager.RemoteUserInfo; import androidx.media3.common.MediaItem; +import androidx.media3.common.MediaMetadata; import androidx.media3.common.util.ConditionVariable; import androidx.media3.common.util.Log; 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.ControllerInfo; 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.MoreExecutors; +import com.google.common.util.concurrent.SettableFuture; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import org.checkerframework.checker.nullness.compatqual.NullableType; /** * Implementation of {@link MediaBrowserServiceCompat} for interoperability between {@link @@ -218,7 +225,11 @@ import java.util.concurrent.atomic.AtomicReference; ListenableFuture>> future = librarySessionImpl.onGetChildrenOnHandler( controller, parentId, page, pageSize, params); - sendLibraryResultWithMediaItemsWhenReady(result, future); + ListenableFuture<@NullableType List> + browserItemsFuture = + Util.transformFutureAsync( + future, createMediaItemsToBrowserItemsAsyncFunction()); + sendLibraryResultWithMediaItemsWhenReady(result, browserItemsFuture); return; } // Cannot distinguish onLoadChildren() why it's called either by @@ -236,7 +247,9 @@ import java.util.concurrent.atomic.AtomicReference; /* page= */ 0, /* pageSize= */ Integer.MAX_VALUE, /* params= */ null); - sendLibraryResultWithMediaItemsWhenReady(result, future); + ListenableFuture<@NullableType List> browserItemsFuture = + Util.transformFutureAsync(future, createMediaItemsToBrowserItemsAsyncFunction()); + sendLibraryResultWithMediaItemsWhenReady(result, browserItemsFuture); }); } @@ -264,7 +277,9 @@ import java.util.concurrent.atomic.AtomicReference; } ListenableFuture> future = librarySessionImpl.onGetItemOnHandler(controller, itemId); - sendLibraryResultWithMediaItemWhenReady(result, future); + ListenableFuture browserItemFuture = + Util.transformFutureAsync(future, createMediaItemToBrowserItemAsyncFunction()); + sendLibraryResultWithMediaItemWhenReady(result, browserItemFuture); }); } @@ -362,17 +377,12 @@ import java.util.concurrent.atomic.AtomicReference; private static void sendLibraryResultWithMediaItemWhenReady( Result result, - ListenableFuture> future) { + ListenableFuture future) { future.addListener( () -> { try { - LibraryResult libraryResult = - checkNotNull(future.get(), "LibraryResult must not be null"); - if (libraryResult.resultCode != RESULT_SUCCESS || libraryResult.value == null) { - result.sendResult(/* result= */ null); - } else { - result.sendResult(MediaUtils.convertToBrowserItem(libraryResult.value)); - } + MediaBrowserCompat.MediaItem mediaItem = future.get(); + result.sendResult(mediaItem); } catch (CancellationException | ExecutionException | InterruptedException unused) { result.sendError(/* extras= */ null); } @@ -382,20 +392,15 @@ import java.util.concurrent.atomic.AtomicReference; private static void sendLibraryResultWithMediaItemsWhenReady( Result> result, - ListenableFuture>> future) { + ListenableFuture<@NullableType List> future) { future.addListener( () -> { try { - LibraryResult> libraryResult = - checkNotNull(future.get(), "LibraryResult must not be null"); - if (libraryResult.resultCode != RESULT_SUCCESS || libraryResult.value == null) { - result.sendResult(/* result= */ null); - } else { - result.sendResult( - MediaUtils.truncateListBySize( - MediaUtils.convertToBrowserItemList(libraryResult.value), - TRANSACTION_SIZE_LIMIT_IN_BYTES)); - } + List mediaItems = future.get(); + result.sendResult( + (mediaItems == null) + ? null + : MediaUtils.truncateListBySize(mediaItems, TRANSACTION_SIZE_LIMIT_IN_BYTES)); } catch (CancellationException | ExecutionException | InterruptedException unused) { result.sendError(/* extras= */ null); } @@ -403,6 +408,130 @@ import java.util.concurrent.atomic.AtomicReference; MoreExecutors.directExecutor()); } + private AsyncFunction< + LibraryResult>, @NullableType List> + createMediaItemsToBrowserItemsAsyncFunction() { + return result -> { + checkNotNull(result, "LibraryResult must not be null"); + SettableFuture<@NullableType List> outputFuture = + SettableFuture.create(); + if (result.resultCode != RESULT_SUCCESS || result.value == null) { + outputFuture.set(null); + return outputFuture; + } + + ImmutableList mediaItems = result.value; + if (mediaItems.isEmpty()) { + outputFuture.set(new ArrayList<>()); + return outputFuture; + } + + List<@NullableType ListenableFuture> 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 bitmapFuture = + librarySessionImpl.getBitmapLoader().decodeBitmap(metadata.artworkData); + bitmapFutures.add(bitmapFuture); + bitmapFuture.addListener(handleBitmapFuturesTask, MoreExecutors.directExecutor()); + } + } + return outputFuture; + }; + } + + private void handleBitmapFuturesAllCompletedAndSetOutputFuture( + List<@NullableType ListenableFuture> bitmapFutures, + List mediaItems, + SettableFuture<@NullableType List> outputFuture) { + List outputMediaItems = new ArrayList<>(); + for (int i = 0; i < bitmapFutures.size(); i++) { + @Nullable ListenableFuture 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 void cancelAllFutures(List<@NullableType ListenableFuture> futures) { + for (int i = 0; i < futures.size(); i++) { + if (futures.get(i) != null) { + futures.get(i).cancel(/* mayInterruptIfRunning= */ false); + } + } + } + + private AsyncFunction, MediaBrowserCompat.@NullableType MediaItem> + createMediaItemToBrowserItemAsyncFunction() { + return result -> { + checkNotNull(result, "LibraryResult must not be null"); + SettableFuture 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 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 void ignoreFuture(Future unused) { // no-op } @@ -504,7 +633,9 @@ import java.util.concurrent.atomic.AtomicReference; ListenableFuture>> future = librarySessionImpl.onGetSearchResultOnHandler( request.controller, request.query, page, pageSize, libraryParams); - sendLibraryResultWithMediaItemsWhenReady(request.result, future); + ListenableFuture<@NullableType List> mediaItemsFuture = + Util.transformFutureAsync(future, createMediaItemsToBrowserItemsAsyncFunction()); + sendLibraryResultWithMediaItemsWhenReady(request.result, mediaItemsFuture); } }); } diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaUtils.java b/libraries/session/src/main/java/androidx/media3/session/MediaUtils.java index ae17cc834f..3f89c5dd73 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaUtils.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaUtils.java @@ -136,9 +136,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; errorMessage, /* cause= */ null, PlaybackException.ERROR_CODE_REMOTE_ERROR); } - /** Converts a {@link MediaItem} to a {@link MediaBrowserCompat.MediaItem}. */ - public static MediaBrowserCompat.MediaItem convertToBrowserItem(MediaItem item) { - MediaDescriptionCompat description = convertToMediaDescriptionCompat(item); + public static MediaBrowserCompat.MediaItem convertToBrowserItem( + MediaItem item, @Nullable Bitmap artworkBitmap) { + MediaDescriptionCompat description = convertToMediaDescriptionCompat(item, artworkBitmap); MediaMetadata metadata = item.mediaMetadata; int flags = 0; 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); } - /** Converts a list of {@link MediaItem} to a list of {@link MediaBrowserCompat.MediaItem}. */ - public static List convertToBrowserItemList(List items) { - List 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}. */ public static MediaItem convertToMediaItem(MediaBrowserCompat.MediaItem item) { return convertToMediaItem(item.getDescription(), item.isBrowsable(), item.isPlayable()); @@ -320,16 +311,32 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; 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) { + 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 = new MediaDescriptionCompat.Builder() .setMediaId(item.mediaId.equals(MediaItem.DEFAULT_MEDIA_ID) ? null : item.mediaId); MediaMetadata metadata = item.mediaMetadata; - if (metadata.artworkData != null) { - Bitmap artwork = - BitmapFactory.decodeByteArray(metadata.artworkData, 0, metadata.artworkData.length); - builder.setIconBitmap(artwork); + if (artworkBitmap != null) { + builder.setIconBitmap(artworkBitmap); } @Nullable Bundle extras = metadata.extras; if (metadata.folderType != null && metadata.folderType != MediaMetadata.FOLDER_TYPE_NONE) { diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserCompatWithMediaLibraryServiceTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserCompatWithMediaLibraryServiceTest.java index e9ec255abc..4e36a19ece 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserCompatWithMediaLibraryServiceTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserCompatWithMediaLibraryServiceTest.java @@ -129,6 +129,7 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(itemRef.get().getMediaId()).isEqualTo(mediaId); assertThat(itemRef.get().isBrowsable()).isTrue(); + assertThat(itemRef.get().getDescription().getIconBitmap()).isNotNull(); } @Test @@ -151,6 +152,7 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(itemRef.get().getMediaId()).isEqualTo(mediaId); assertThat(itemRef.get().isPlayable()).isTrue(); + assertThat(itemRef.get().getDescription().getIconBitmap()).isNotNull(); } @Test @@ -181,6 +183,7 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest BundleSubject.assertThat(description.getExtras()) .string(METADATA_EXTRA_KEY) .isEqualTo(METADATA_EXTRA_VALUE); + assertThat(description.getIconBitmap()).isNotNull(); } @Test @@ -245,6 +248,7 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest EXTRAS_KEY_COMPLETION_STATUS, /* defaultValue= */ EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED + 1)) .isEqualTo(EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED); + assertThat(mediaItem.getDescription().getIconBitmap()).isNotNull(); } } @@ -311,6 +315,7 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest int relativeIndex = originalIndex - fromIndex; assertThat(children.get(relativeIndex).getMediaId()) .isEqualTo(GET_CHILDREN_RESULT.get(originalIndex)); + assertThat(children.get(relativeIndex).getDescription().getIconBitmap()).isNotNull(); } latch.countDown(); } diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaUtilsTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaUtilsTest.java index 1d53a84794..d7a8fca105 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaUtilsTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaUtilsTest.java @@ -32,7 +32,6 @@ import android.support.v4.media.RatingCompat; import android.support.v4.media.session.MediaControllerCompat; import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.PlaybackStateCompat; -import android.text.TextUtils; import androidx.annotation.Nullable; import androidx.media.AudioAttributesCompat; import androidx.media3.common.AudioAttributes; @@ -71,23 +70,6 @@ public final class MediaUtilsTest { 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 public void convertToMediaItem_browserItemToMediaItem() { String mediaId = "testId"; @@ -115,18 +97,6 @@ public final class MediaUtilsTest { assertThat(mediaItem.mediaMetadata.title.toString()).isEqualTo(title); } - @Test - public void convertToBrowserItemList() { - int size = 3; - List mediaItems = MediaTestUtils.createMediaItems(size); - List 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 public void convertBrowserItemListToMediaItemList() { int size = 3; diff --git a/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaLibraryService.java b/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaLibraryService.java index cfad2d7550..e3023a00a2 100644 --- a/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaLibraryService.java +++ b/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaLibraryService.java @@ -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_WITH_NON_SUBSCRIBED_ID; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.Assert.fail; import android.app.PendingIntent; 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.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import java.io.IOException; import java.util.ArrayList; import java.util.List; 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 = "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 = new MediaItem.Builder() .setMediaId(ROOT_ID) @@ -115,6 +119,8 @@ public class MockMediaLibraryService extends MediaLibraryService { @Nullable private static LibraryParams expectedParams; + @Nullable private static byte[] testArtworkData; + MediaLibrarySession session; TestHandler handler; HandlerThread handlerThread; @@ -238,7 +244,8 @@ public class MockMediaLibraryService extends MediaLibraryService { LibraryResult.ofItem(createBrowsableMediaItem(mediaId), /* params= */ null)); case MEDIA_ID_GET_PLAYABLE_ITEM: return Futures.immediateFuture( - LibraryResult.ofItem(createPlayableMediaItem(mediaId), /* params= */ null)); + LibraryResult.ofItem( + createPlayableMediaItemWithArtworkData(mediaId), /* params= */ null)); case MEDIA_ID_GET_ITEM_WITH_METADATA: return Futures.immediateFuture( 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. List result = new ArrayList<>(); for (int i = 0; i < paginatedMediaIdList.size(); i++) { - result.add(createPlayableMediaItem(paginatedMediaIdList.get(i))); + result.add(createPlayableMediaItemWithArtworkData(paginatedMediaIdList.get(i))); } return result; } - private static MediaItem createBrowsableMediaItem(String mediaId) { + private MediaItem createBrowsableMediaItem(String mediaId) { MediaMetadata mediaMetadata = new MediaMetadata.Builder() .setFolderType(MediaMetadata.FOLDER_TYPE_MIXED) .setIsPlayable(false) + .setArtworkData(getArtworkData(), MediaMetadata.PICTURE_TYPE_FRONT_COVER) .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) { Bundle extras = new Bundle(); 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(); } - private static MediaItem createMediaItemWithMetadata(String mediaId) { - MediaMetadata mediaMetadata = MediaTestUtils.createMediaMetadata(); + private MediaItem createMediaItemWithMetadata(String mediaId) { + MediaMetadata mediaMetadataWithArtwork = + MediaTestUtils.createMediaMetadata() + .buildUpon() + .setArtworkData(getArtworkData(), MediaMetadata.PICTURE_TYPE_FRONT_COVER) + .build(); return new MediaItem.Builder() .setMediaId(mediaId) .setRequestMetadata( new MediaItem.RequestMetadata.Builder() .setMediaUri(CommonConstants.METADATA_MEDIA_URI) .build()) - .setMediaMetadata(mediaMetadata) + .setMediaMetadata(mediaMetadataWithArtwork) .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; + } }