diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 94b1fb9fbd..14443b966d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -32,6 +32,9 @@ Release notes ID3 v2.4. * Add `MediaMetadata.mediaType` to denote the type of content or the type of folder described by the metadata. + * Add `MediaMetadata.isBrowsable` as a replacement for + `MediaMetadata.folderType`. The folder type will be deprecated in the + next release. * Remove deprecated symbols: * Remove `DefaultAudioSink` constructors, use `DefaultAudioSink.Builder` instead. diff --git a/demos/session/src/main/java/androidx/media3/demo/session/MediaItemTree.kt b/demos/session/src/main/java/androidx/media3/demo/session/MediaItemTree.kt index d1ece8ba12..a1a6c6c187 100644 --- a/demos/session/src/main/java/androidx/media3/demo/session/MediaItemTree.kt +++ b/demos/session/src/main/java/androidx/media3/demo/session/MediaItemTree.kt @@ -20,11 +20,6 @@ import android.net.Uri import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem.SubtitleConfiguration import androidx.media3.common.MediaMetadata -import androidx.media3.common.MediaMetadata.FOLDER_TYPE_ALBUMS -import androidx.media3.common.MediaMetadata.FOLDER_TYPE_ARTISTS -import androidx.media3.common.MediaMetadata.FOLDER_TYPE_GENRES -import androidx.media3.common.MediaMetadata.FOLDER_TYPE_MIXED -import androidx.media3.common.MediaMetadata.FOLDER_TYPE_NONE import androidx.media3.common.util.Util import com.google.common.collect.ImmutableList import org.json.JSONObject @@ -67,7 +62,8 @@ object MediaItemTree { title: String, mediaId: String, isPlayable: Boolean, - @MediaMetadata.FolderType folderType: Int, + isBrowsable: Boolean, + mediaType: @MediaMetadata.MediaType Int, subtitleConfigurations: List = mutableListOf(), album: String? = null, artist: String? = null, @@ -81,9 +77,10 @@ object MediaItemTree { .setTitle(title) .setArtist(artist) .setGenre(genre) - .setFolderType(folderType) + .setIsBrowsable(isBrowsable) .setIsPlayable(isPlayable) .setArtworkUri(imageUri) + .setMediaType(mediaType) .build() return MediaItem.Builder() @@ -109,7 +106,8 @@ object MediaItemTree { title = "Root Folder", mediaId = ROOT_ID, isPlayable = false, - folderType = FOLDER_TYPE_MIXED + isBrowsable = true, + mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED ) ) treeNodes[ALBUM_ID] = @@ -118,7 +116,8 @@ object MediaItemTree { title = "Album Folder", mediaId = ALBUM_ID, isPlayable = false, - folderType = FOLDER_TYPE_MIXED + isBrowsable = true, + mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_ALBUMS ) ) treeNodes[ARTIST_ID] = @@ -127,7 +126,8 @@ object MediaItemTree { title = "Artist Folder", mediaId = ARTIST_ID, isPlayable = false, - folderType = FOLDER_TYPE_MIXED + isBrowsable = true, + mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_ARTISTS ) ) treeNodes[GENRE_ID] = @@ -136,7 +136,8 @@ object MediaItemTree { title = "Genre Folder", mediaId = GENRE_ID, isPlayable = false, - folderType = FOLDER_TYPE_MIXED + isBrowsable = true, + mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_GENRES ) ) treeNodes[ROOT_ID]!!.addChild(ALBUM_ID) @@ -188,7 +189,8 @@ object MediaItemTree { title = title, mediaId = idInTree, isPlayable = true, - folderType = FOLDER_TYPE_NONE, + isBrowsable = false, + mediaType = MediaMetadata.MEDIA_TYPE_MUSIC, subtitleConfigurations, album = album, artist = artist, @@ -207,7 +209,8 @@ object MediaItemTree { title = album, mediaId = albumFolderIdInTree, isPlayable = true, - folderType = FOLDER_TYPE_ALBUMS, + isBrowsable = true, + mediaType = MediaMetadata.MEDIA_TYPE_ALBUM, subtitleConfigurations ) ) @@ -223,7 +226,8 @@ object MediaItemTree { title = artist, mediaId = artistFolderIdInTree, isPlayable = true, - folderType = FOLDER_TYPE_ARTISTS, + isBrowsable = true, + mediaType = MediaMetadata.MEDIA_TYPE_ARTIST, subtitleConfigurations ) ) @@ -239,7 +243,8 @@ object MediaItemTree { title = genre, mediaId = genreFolderIdInTree, isPlayable = true, - folderType = FOLDER_TYPE_GENRES, + isBrowsable = true, + mediaType = MediaMetadata.MEDIA_TYPE_GENRE, subtitleConfigurations ) ) @@ -262,7 +267,7 @@ object MediaItemTree { fun getRandomItem(): MediaItem { var curRoot = getRootItem() - while (curRoot.mediaMetadata.folderType != FOLDER_TYPE_NONE) { + while (curRoot.mediaMetadata.isBrowsable == true) { val children = getChildren(curRoot.mediaId)!! curRoot = children.random() } diff --git a/libraries/common/src/main/java/androidx/media3/common/MediaMetadata.java b/libraries/common/src/main/java/androidx/media3/common/MediaMetadata.java index b1d23866a0..470ed7a71c 100644 --- a/libraries/common/src/main/java/androidx/media3/common/MediaMetadata.java +++ b/libraries/common/src/main/java/androidx/media3/common/MediaMetadata.java @@ -61,6 +61,7 @@ public final class MediaMetadata implements Bundleable { @Nullable private Integer trackNumber; @Nullable private Integer totalTrackCount; @Nullable private @FolderType Integer folderType; + @Nullable private Boolean isBrowsable; @Nullable private Boolean isPlayable; @Nullable private Integer recordingYear; @Nullable private Integer recordingMonth; @@ -97,6 +98,7 @@ public final class MediaMetadata implements Bundleable { this.trackNumber = mediaMetadata.trackNumber; this.totalTrackCount = mediaMetadata.totalTrackCount; this.folderType = mediaMetadata.folderType; + this.isBrowsable = mediaMetadata.isBrowsable; this.isPlayable = mediaMetadata.isPlayable; this.recordingYear = mediaMetadata.recordingYear; this.recordingMonth = mediaMetadata.recordingMonth; @@ -246,13 +248,26 @@ public final class MediaMetadata implements Bundleable { return this; } - /** Sets the {@link FolderType}. */ + /** + * Sets the {@link FolderType}. + * + *

This method will be deprecated. Use {@link #setIsBrowsable} to indicate if an item is a + * browsable folder and use {@link #setMediaType} to indicate the type of the folder. + */ @CanIgnoreReturnValue public Builder setFolderType(@Nullable @FolderType Integer folderType) { this.folderType = folderType; return this; } + /** Sets whether the media is a browsable folder. */ + @UnstableApi + @CanIgnoreReturnValue + public Builder setIsBrowsable(@Nullable Boolean isBrowsable) { + this.isBrowsable = isBrowsable; + return this; + } + /** Sets whether the media is playable. */ @CanIgnoreReturnValue public Builder setIsPlayable(@Nullable Boolean isPlayable) { @@ -491,6 +506,9 @@ public final class MediaMetadata implements Bundleable { if (mediaMetadata.folderType != null) { setFolderType(mediaMetadata.folderType); } + if (mediaMetadata.isBrowsable != null) { + setIsBrowsable(mediaMetadata.isBrowsable); + } if (mediaMetadata.isPlayable != null) { setIsPlayable(mediaMetadata.isPlayable); } @@ -874,9 +892,16 @@ public final class MediaMetadata implements Bundleable { @Nullable public final Integer trackNumber; /** Optional total number of tracks. */ @Nullable public final Integer totalTrackCount; - /** Optional {@link FolderType}. */ + /** + * Optional {@link FolderType}. + * + *

This field will be deprecated. Use {@link #isBrowsable} to indicate if an item is a + * browsable folder and use {@link #mediaType} to indicate the type of the folder. + */ @Nullable public final @FolderType Integer folderType; - /** Optional boolean for media playability. */ + /** Optional boolean to indicate that the media is a browsable folder. */ + @UnstableApi @Nullable public final Boolean isBrowsable; + /** Optional boolean to indicate that the media is playable. */ @Nullable public final Boolean isPlayable; /** * @deprecated Use {@link #recordingYear} instead. @@ -939,6 +964,22 @@ public final class MediaMetadata implements Bundleable { @Nullable public final Bundle extras; private MediaMetadata(Builder builder) { + // Handle compatibility for deprecated fields. + @Nullable Boolean isBrowsable = builder.isBrowsable; + @Nullable Integer folderType = builder.folderType; + @Nullable Integer mediaType = builder.mediaType; + if (isBrowsable != null) { + if (!isBrowsable) { + folderType = FOLDER_TYPE_NONE; + } else if (folderType == null || folderType == FOLDER_TYPE_NONE) { + folderType = mediaType != null ? getFolderTypeFromMediaType(mediaType) : FOLDER_TYPE_MIXED; + } + } else if (folderType != null) { + isBrowsable = folderType != FOLDER_TYPE_NONE; + if (isBrowsable && mediaType == null) { + mediaType = getMediaTypeFromFolderType(folderType); + } + } this.title = builder.title; this.artist = builder.artist; this.albumTitle = builder.albumTitle; @@ -953,7 +994,8 @@ public final class MediaMetadata implements Bundleable { this.artworkUri = builder.artworkUri; this.trackNumber = builder.trackNumber; this.totalTrackCount = builder.totalTrackCount; - this.folderType = builder.folderType; + this.folderType = folderType; + this.isBrowsable = isBrowsable; this.isPlayable = builder.isPlayable; this.year = builder.recordingYear; this.recordingYear = builder.recordingYear; @@ -970,7 +1012,7 @@ public final class MediaMetadata implements Bundleable { this.genre = builder.genre; this.compilation = builder.compilation; this.station = builder.station; - this.mediaType = builder.mediaType; + this.mediaType = mediaType; this.extras = builder.extras; } @@ -1003,6 +1045,7 @@ public final class MediaMetadata implements Bundleable { && Util.areEqual(trackNumber, that.trackNumber) && Util.areEqual(totalTrackCount, that.totalTrackCount) && Util.areEqual(folderType, that.folderType) + && Util.areEqual(isBrowsable, that.isBrowsable) && Util.areEqual(isPlayable, that.isPlayable) && Util.areEqual(recordingYear, that.recordingYear) && Util.areEqual(recordingMonth, that.recordingMonth) @@ -1039,6 +1082,7 @@ public final class MediaMetadata implements Bundleable { trackNumber, totalTrackCount, folderType, + isBrowsable, isPlayable, recordingYear, recordingMonth, @@ -1095,6 +1139,7 @@ public final class MediaMetadata implements Bundleable { FIELD_COMPILATION, FIELD_STATION, FIELD_MEDIA_TYPE, + FIELD_IS_BROWSABLE, FIELD_EXTRAS, }) private @interface FieldNumber {} @@ -1131,6 +1176,7 @@ public final class MediaMetadata implements Bundleable { private static final int FIELD_ARTWORK_DATA_TYPE = 29; private static final int FIELD_STATION = 30; private static final int FIELD_MEDIA_TYPE = 31; + private static final int FIELD_IS_BROWSABLE = 32; private static final int FIELD_EXTRAS = 1000; @UnstableApi @@ -1168,6 +1214,9 @@ public final class MediaMetadata implements Bundleable { if (folderType != null) { bundle.putInt(keyForField(FIELD_FOLDER_TYPE), folderType); } + if (isBrowsable != null) { + bundle.putBoolean(keyForField(FIELD_IS_BROWSABLE), isBrowsable); + } if (isPlayable != null) { bundle.putBoolean(keyForField(FIELD_IS_PLAYABLE), isPlayable); } @@ -1255,6 +1304,9 @@ public final class MediaMetadata implements Bundleable { if (bundle.containsKey(keyForField(FIELD_FOLDER_TYPE))) { builder.setFolderType(bundle.getInt(keyForField(FIELD_FOLDER_TYPE))); } + if (bundle.containsKey(keyForField(FIELD_IS_BROWSABLE))) { + builder.setIsBrowsable(bundle.getBoolean(keyForField(FIELD_IS_BROWSABLE))); + } if (bundle.containsKey(keyForField(FIELD_IS_PLAYABLE))) { builder.setIsPlayable(bundle.getBoolean(keyForField(FIELD_IS_PLAYABLE))); } @@ -1292,4 +1344,74 @@ public final class MediaMetadata implements Bundleable { private static String keyForField(@FieldNumber int field) { return Integer.toString(field, Character.MAX_RADIX); } + + private static @FolderType int getFolderTypeFromMediaType(@MediaType int mediaType) { + switch (mediaType) { + case MEDIA_TYPE_ALBUM: + case MEDIA_TYPE_ARTIST: + case MEDIA_TYPE_AUDIO_BOOK: + case MEDIA_TYPE_AUDIO_BOOK_CHAPTER: + case MEDIA_TYPE_FOLDER_MOVIES: + case MEDIA_TYPE_FOLDER_NEWS: + case MEDIA_TYPE_FOLDER_RADIO_STATIONS: + case MEDIA_TYPE_FOLDER_TRAILERS: + case MEDIA_TYPE_FOLDER_VIDEOS: + case MEDIA_TYPE_GENRE: + case MEDIA_TYPE_MOVIE: + case MEDIA_TYPE_MUSIC: + case MEDIA_TYPE_NEWS: + case MEDIA_TYPE_PLAYLIST: + case MEDIA_TYPE_PODCAST: + case MEDIA_TYPE_PODCAST_EPISODE: + case MEDIA_TYPE_RADIO_STATION: + case MEDIA_TYPE_TRAILER: + case MEDIA_TYPE_TV_CHANNEL: + case MEDIA_TYPE_TV_SEASON: + case MEDIA_TYPE_TV_SERIES: + case MEDIA_TYPE_TV_SHOW: + case MEDIA_TYPE_VIDEO: + case MEDIA_TYPE_YEAR: + return FOLDER_TYPE_TITLES; + case MEDIA_TYPE_FOLDER_ALBUMS: + return FOLDER_TYPE_ALBUMS; + case MEDIA_TYPE_FOLDER_ARTISTS: + return FOLDER_TYPE_ARTISTS; + case MEDIA_TYPE_FOLDER_GENRES: + return FOLDER_TYPE_GENRES; + case MEDIA_TYPE_FOLDER_PLAYLISTS: + return FOLDER_TYPE_PLAYLISTS; + case MEDIA_TYPE_FOLDER_YEARS: + return FOLDER_TYPE_YEARS; + case MEDIA_TYPE_FOLDER_AUDIO_BOOKS: + case MEDIA_TYPE_FOLDER_MIXED: + case MEDIA_TYPE_FOLDER_TV_CHANNELS: + case MEDIA_TYPE_FOLDER_TV_SERIES: + case MEDIA_TYPE_FOLDER_TV_SHOWS: + case MEDIA_TYPE_FOLDER_PODCASTS: + case MEDIA_TYPE_MIXED: + default: + return FOLDER_TYPE_MIXED; + } + } + + private static @MediaType int getMediaTypeFromFolderType(@FolderType int folderType) { + switch (folderType) { + case FOLDER_TYPE_ALBUMS: + return MEDIA_TYPE_FOLDER_ALBUMS; + case FOLDER_TYPE_ARTISTS: + return MEDIA_TYPE_FOLDER_ARTISTS; + case FOLDER_TYPE_GENRES: + return MEDIA_TYPE_FOLDER_GENRES; + case FOLDER_TYPE_PLAYLISTS: + return MEDIA_TYPE_FOLDER_PLAYLISTS; + case FOLDER_TYPE_TITLES: + return MEDIA_TYPE_MIXED; + case FOLDER_TYPE_YEARS: + return MEDIA_TYPE_FOLDER_YEARS; + case FOLDER_TYPE_MIXED: + case FOLDER_TYPE_NONE: + default: + return MEDIA_TYPE_FOLDER_MIXED; + } + } } diff --git a/libraries/common/src/test/java/androidx/media3/common/MediaMetadataTest.java b/libraries/common/src/test/java/androidx/media3/common/MediaMetadataTest.java index 4d66cd922a..f3a7418fc7 100644 --- a/libraries/common/src/test/java/androidx/media3/common/MediaMetadataTest.java +++ b/libraries/common/src/test/java/androidx/media3/common/MediaMetadataTest.java @@ -49,6 +49,7 @@ public class MediaMetadataTest { assertThat(mediaMetadata.trackNumber).isNull(); assertThat(mediaMetadata.totalTrackCount).isNull(); assertThat(mediaMetadata.folderType).isNull(); + assertThat(mediaMetadata.isBrowsable).isNull(); assertThat(mediaMetadata.isPlayable).isNull(); assertThat(mediaMetadata.recordingYear).isNull(); assertThat(mediaMetadata.recordingMonth).isNull(); @@ -115,6 +116,61 @@ public class MediaMetadataTest { assertThat(fromBundle.extras.getString(EXTRAS_KEY)).isEqualTo(EXTRAS_VALUE); } + @Test + public void builderSetFolderType_toNone_setsIsBrowsableToFalse() { + MediaMetadata mediaMetadata = + new MediaMetadata.Builder().setFolderType(MediaMetadata.FOLDER_TYPE_NONE).build(); + + assertThat(mediaMetadata.isBrowsable).isFalse(); + } + + @Test + public void builderSetFolderType_toNotNone_setsIsBrowsableToTrueAndMatchingMediaType() { + MediaMetadata mediaMetadata = + new MediaMetadata.Builder().setFolderType(MediaMetadata.FOLDER_TYPE_PLAYLISTS).build(); + + assertThat(mediaMetadata.isBrowsable).isTrue(); + assertThat(mediaMetadata.mediaType).isEqualTo(MediaMetadata.MEDIA_TYPE_FOLDER_PLAYLISTS); + } + + @Test + public void + builderSetFolderType_toNotNoneWithManualMediaType_setsIsBrowsableToTrueAndDoesNotOverrideMediaType() { + MediaMetadata mediaMetadata = + new MediaMetadata.Builder() + .setMediaType(MediaMetadata.MEDIA_TYPE_FOLDER_PODCASTS) + .setFolderType(MediaMetadata.FOLDER_TYPE_PLAYLISTS) + .build(); + + assertThat(mediaMetadata.isBrowsable).isTrue(); + assertThat(mediaMetadata.mediaType).isEqualTo(MediaMetadata.MEDIA_TYPE_FOLDER_PODCASTS); + } + + @Test + public void builderSetIsBrowsable_toTrueWithoutMediaType_setsFolderTypeToMixed() { + MediaMetadata mediaMetadata = new MediaMetadata.Builder().setIsBrowsable(true).build(); + + assertThat(mediaMetadata.folderType).isEqualTo(MediaMetadata.FOLDER_TYPE_MIXED); + } + + @Test + public void builderSetIsBrowsable_toTrueWithMediaType_setsFolderTypeToMatchMediaType() { + MediaMetadata mediaMetadata = + new MediaMetadata.Builder() + .setIsBrowsable(true) + .setMediaType(MediaMetadata.MEDIA_TYPE_FOLDER_ARTISTS) + .build(); + + assertThat(mediaMetadata.folderType).isEqualTo(MediaMetadata.FOLDER_TYPE_ARTISTS); + } + + @Test + public void builderSetFolderType_toFalse_setsFolderTypeToNone() { + MediaMetadata mediaMetadata = new MediaMetadata.Builder().setIsBrowsable(false).build(); + + assertThat(mediaMetadata.folderType).isEqualTo(MediaMetadata.FOLDER_TYPE_NONE); + } + private static MediaMetadata getFullyPopulatedMediaMetadata() { Bundle extras = new Bundle(); extras.putString(EXTRAS_KEY, EXTRAS_VALUE); @@ -135,6 +191,7 @@ public class MediaMetadataTest { .setTrackNumber(4) .setTotalTrackCount(12) .setFolderType(MediaMetadata.FOLDER_TYPE_PLAYLISTS) + .setIsBrowsable(true) .setIsPlayable(true) .setRecordingYear(2000) .setRecordingMonth(11) diff --git a/libraries/session/src/main/java/androidx/media3/session/LibraryResult.java b/libraries/session/src/main/java/androidx/media3/session/LibraryResult.java index 2984dbc604..471d0200eb 100644 --- a/libraries/session/src/main/java/androidx/media3/session/LibraryResult.java +++ b/libraries/session/src/main/java/androidx/media3/session/LibraryResult.java @@ -175,8 +175,8 @@ public final class LibraryResult implements Bundleable { /** * Creates an instance with a media item and {@link #resultCode}{@code ==}{@link #RESULT_SUCCESS}. * - *

The {@link MediaItem#mediaMetadata} must specify {@link MediaMetadata#folderType} and {@link - * MediaMetadata#isPlayable} fields. + *

The {@link MediaItem#mediaMetadata} must specify {@link MediaMetadata#isBrowsable} (or + * {@link MediaMetadata#folderType}) and {@link MediaMetadata#isPlayable} fields. * * @param item The media item. * @param params The optional parameters to describe the media item. @@ -192,7 +192,8 @@ public final class LibraryResult implements Bundleable { * #RESULT_SUCCESS}. * *

The {@link MediaItem#mediaMetadata} of each item in the list must specify {@link - * MediaMetadata#folderType} and {@link MediaMetadata#isPlayable} fields. + * MediaMetadata#isBrowsable} (or {@link MediaMetadata#folderType}) and {@link + * MediaMetadata#isPlayable} fields. * * @param items The list of media items. * @param params The optional parameters to describe the list of media items. @@ -255,7 +256,7 @@ public final class LibraryResult implements Bundleable { private static void verifyMediaItem(MediaItem item) { checkNotEmpty(item.mediaId, "mediaId must not be empty"); - checkArgument(item.mediaMetadata.folderType != null, "mediaMetadata must specify folderType"); + checkArgument(item.mediaMetadata.isBrowsable != null, "mediaMetadata must specify isBrowsable"); checkArgument(item.mediaMetadata.isPlayable != null, "mediaMetadata must specify isPlayable"); } diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaBrowserImplLegacy.java b/libraries/session/src/main/java/androidx/media3/session/MediaBrowserImplLegacy.java index 742c31e879..fc924af3d9 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaBrowserImplLegacy.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaBrowserImplLegacy.java @@ -311,7 +311,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; String mediaId = browserCompat.getRoot(); MediaMetadata mediaMetadata = new MediaMetadata.Builder() - .setFolderType(MediaMetadata.FOLDER_TYPE_MIXED) + .setIsBrowsable(true) + .setMediaType(MediaMetadata.MEDIA_TYPE_FOLDER_MIXED) .setIsPlayable(false) .setExtras(browserCompat.getExtras()) .build(); diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaConstants.java b/libraries/session/src/main/java/androidx/media3/session/MediaConstants.java index 8dda126ce4..5da473a821 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaConstants.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaConstants.java @@ -213,7 +213,7 @@ public final class MediaConstants { * {@link MediaBrowser#getLibraryRoot}, the preference applies to all playable items within the * browse tree. * - *

If exposed through {@link MediaMetadata#extras} of a {@linkplain MediaMetadata#folderType + *

If exposed through {@link MediaMetadata#extras} of a {@linkplain MediaMetadata#isBrowsable * browsable media item}, the preference applies to only the immediate playable children. It takes * precedence over preferences received with {@link MediaBrowser#getLibraryRoot}. * @@ -238,7 +238,7 @@ public final class MediaConstants { * {@link MediaBrowser#getLibraryRoot}, the preference applies to all browsable items within the * browse tree. * - *

If exposed through {@link MediaMetadata#extras} of a {@linkplain MediaMetadata#folderType + *

If exposed through {@link MediaMetadata#extras} of a {@linkplain MediaMetadata#isBrowsable * browsable media item}, the preference applies to only the immediate browsable children. It * takes precedence over preferences received with {@link * MediaBrowser#getLibraryRoot(LibraryParams)}. diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaLibraryService.java b/libraries/session/src/main/java/androidx/media3/session/MediaLibraryService.java index bb6b8ebfa9..48442529ff 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaLibraryService.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaLibraryService.java @@ -123,8 +123,9 @@ public abstract class MediaLibraryService extends MediaSessionService { * An extended {@link MediaSession.Callback} for the {@link MediaLibrarySession}. * *

When you return {@link LibraryResult} with {@link MediaItem media items}, each item must - * have valid {@link MediaItem#mediaId} and specify {@link MediaMetadata#folderType} and {@link - * MediaMetadata#isPlayable} in its {@link MediaItem#mediaMetadata}. + * have valid {@link MediaItem#mediaId} and specify {@link MediaMetadata#isBrowsable} (or {@link + * MediaMetadata#folderType}) and {@link MediaMetadata#isPlayable} in its {@link + * MediaItem#mediaMetadata}. */ public interface Callback extends MediaSession.Callback { 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 c805129dd4..919d552178 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaUtils.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaUtils.java @@ -145,7 +145,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; MediaDescriptionCompat description = convertToMediaDescriptionCompat(item, artworkBitmap); MediaMetadata metadata = item.mediaMetadata; int flags = 0; - if (metadata.folderType != null && metadata.folderType != MediaMetadata.FOLDER_TYPE_NONE) { + if (metadata.isBrowsable != null && metadata.isBrowsable) { flags |= MediaBrowserCompat.MediaItem.FLAG_BROWSABLE; } if (metadata.isPlayable != null && metadata.isPlayable) { @@ -375,11 +375,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; if (queueTitle == null) { return MediaMetadata.EMPTY; } - return new MediaMetadata.Builder() - .setTitle(queueTitle) - .setFolderType(MediaMetadata.FOLDER_TYPE_MIXED) - .setIsPlayable(true) - .build(); + return new MediaMetadata.Builder().setTitle(queueTitle).build(); } public static MediaMetadata convertToMediaMetadata( @@ -417,20 +413,22 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; builder.setArtworkData(artworkData, MediaMetadata.PICTURE_TYPE_FRONT_COVER); } - @Nullable Bundle extras = descriptionCompat.getExtras(); - builder.setExtras(extras); + @Nullable Bundle compatExtras = descriptionCompat.getExtras(); + @Nullable Bundle extras = compatExtras == null ? null : new Bundle(compatExtras); if (extras != null && extras.containsKey(MediaDescriptionCompat.EXTRA_BT_FOLDER_TYPE)) { builder.setFolderType( convertToFolderType(extras.getLong(MediaDescriptionCompat.EXTRA_BT_FOLDER_TYPE))); - } else if (browsable) { - builder.setFolderType(MediaMetadata.FOLDER_TYPE_MIXED); - } else { - builder.setFolderType(MediaMetadata.FOLDER_TYPE_NONE); + extras.remove(MediaDescriptionCompat.EXTRA_BT_FOLDER_TYPE); } + builder.setIsBrowsable(browsable); if (extras != null && extras.containsKey(MediaConstants.EXTRAS_KEY_MEDIA_TYPE_COMPAT)) { builder.setMediaType((int) extras.getLong(MediaConstants.EXTRAS_KEY_MEDIA_TYPE_COMPAT)); + extras.remove(MediaConstants.EXTRAS_KEY_MEDIA_TYPE_COMPAT); + } + if (extras != null && !extras.isEmpty()) { + builder.setExtras(extras); } builder.setIsPlayable(playable); @@ -501,12 +499,13 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; } } - if (metadataCompat.containsKey(MediaMetadataCompat.METADATA_KEY_BT_FOLDER_TYPE)) { + boolean isBrowsable = + metadataCompat.containsKey(MediaMetadataCompat.METADATA_KEY_BT_FOLDER_TYPE); + builder.setIsBrowsable(isBrowsable); + if (isBrowsable) { builder.setFolderType( convertToFolderType( metadataCompat.getLong(MediaMetadataCompat.METADATA_KEY_BT_FOLDER_TYPE))); - } else { - builder.setFolderType(MediaMetadata.FOLDER_TYPE_NONE); } if (metadataCompat.containsKey(MediaConstants.EXTRAS_KEY_MEDIA_TYPE_COMPAT)) { @@ -653,7 +652,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; } else if (extraBtFolderType == MediaDescriptionCompat.BT_FOLDER_TYPE_YEARS) { return MediaMetadata.FOLDER_TYPE_YEARS; } else { - return MediaMetadata.FOLDER_TYPE_NONE; + return MediaMetadata.FOLDER_TYPE_MIXED; } } diff --git a/libraries/session/src/test/java/androidx/media3/session/LibraryResultTest.java b/libraries/session/src/test/java/androidx/media3/session/LibraryResultTest.java index e1ab29d946..a4d7afc33c 100644 --- a/libraries/session/src/test/java/androidx/media3/session/LibraryResultTest.java +++ b/libraries/session/src/test/java/androidx/media3/session/LibraryResultTest.java @@ -30,17 +30,14 @@ public class LibraryResultTest { @Test public void constructor_mediaItemWithoutMediaId_throwsIAE() { MediaMetadata metadata = - new MediaMetadata.Builder() - .setFolderType(MediaMetadata.FOLDER_TYPE_MIXED) - .setIsPlayable(true) - .build(); + new MediaMetadata.Builder().setIsBrowsable(true).setIsPlayable(true).build(); MediaItem item = new MediaItem.Builder().setMediaMetadata(metadata).build(); assertThrows( IllegalArgumentException.class, () -> LibraryResult.ofItem(item, /* params= */ null)); } @Test - public void constructor_mediaItemWithoutFolderType_throwsIAE() { + public void constructor_mediaItemWithoutIsBrowsable_throwsIAE() { MediaMetadata metadata = new MediaMetadata.Builder().setIsPlayable(true).build(); MediaItem item = new MediaItem.Builder().setMediaId("id").setMediaMetadata(metadata).build(); assertThrows( @@ -49,8 +46,7 @@ public class LibraryResultTest { @Test public void constructor_mediaItemWithoutIsPlayable_throwsIAE() { - MediaMetadata metadata = - new MediaMetadata.Builder().setFolderType(MediaMetadata.FOLDER_TYPE_MIXED).build(); + MediaMetadata metadata = new MediaMetadata.Builder().setIsBrowsable(true).build(); MediaItem item = new MediaItem.Builder().setMediaId("id").setMediaMetadata(metadata).build(); assertThrows( IllegalArgumentException.class, () -> LibraryResult.ofItem(item, /* params= */ null)); diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserListenerTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserListenerTest.java index 45c7171ccb..5884f6c6ba 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserListenerTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserListenerTest.java @@ -42,7 +42,6 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import android.os.Bundle; import androidx.annotation.Nullable; import androidx.media3.common.MediaItem; -import androidx.media3.common.MediaMetadata; import androidx.media3.session.MediaLibraryService.LibraryParams; import androidx.media3.test.session.common.MediaBrowserConstants; import androidx.media3.test.session.common.TestUtils; @@ -155,7 +154,7 @@ public class MediaBrowserListenerTest extends MediaControllerListenerTest { assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS); assertThat(result.value.mediaId).isEqualTo(mediaId); - assertThat(result.value.mediaMetadata.folderType).isEqualTo(MediaMetadata.FOLDER_TYPE_MIXED); + assertThat(result.value.mediaMetadata.isBrowsable).isTrue(); } @Test diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserServiceCompatCallbackWithMediaBrowserTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserServiceCompatCallbackWithMediaBrowserTest.java index 043b654f5c..873d2a4441 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserServiceCompatCallbackWithMediaBrowserTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserServiceCompatCallbackWithMediaBrowserTest.java @@ -142,7 +142,7 @@ public class MediaBrowserServiceCompatCallbackWithMediaBrowserTest { assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(result.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS); assertItemEquals(testItem, result.value); - assertThat(result.value.mediaMetadata.folderType).isEqualTo(MediaMetadata.FOLDER_TYPE_MIXED); + assertThat(result.value.mediaMetadata.isBrowsable).isTrue(); } @Test diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionServiceNotificationTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionServiceNotificationTest.java index 4180c3a357..850cf31860 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionServiceNotificationTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionServiceNotificationTest.java @@ -138,7 +138,6 @@ public class MediaSessionServiceNotificationTest { .setTitle("Test Song Name") .setArtist("Test Artist Name") .setArtworkData(artworkData) - .setFolderType(MediaMetadata.FOLDER_TYPE_NONE) .setIsPlayable(true) .build(); } @@ -147,7 +146,6 @@ public class MediaSessionServiceNotificationTest { return new MediaMetadata.Builder() .setTitle("New Song Name") .setArtist("New Artist Name") - .setFolderType(MediaMetadata.FOLDER_TYPE_NONE) .setIsPlayable(true) .build(); } diff --git a/libraries/test_session_current/src/main/java/androidx/media3/session/MediaTestUtils.java b/libraries/test_session_current/src/main/java/androidx/media3/session/MediaTestUtils.java index 2a6af52728..bd82b76e2a 100644 --- a/libraries/test_session_current/src/main/java/androidx/media3/session/MediaTestUtils.java +++ b/libraries/test_session_current/src/main/java/androidx/media3/session/MediaTestUtils.java @@ -56,8 +56,8 @@ public final class MediaTestUtils { public static MediaItem createMediaItem(String mediaId) { MediaMetadata mediaMetadata = new MediaMetadata.Builder() - .setFolderType(MediaMetadata.FOLDER_TYPE_TITLES) .setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST) + .setIsBrowsable(false) .setIsPlayable(true) .build(); return new MediaItem.Builder().setMediaId(mediaId).setMediaMetadata(mediaMetadata).build(); @@ -66,8 +66,8 @@ public final class MediaTestUtils { public static MediaItem createMediaItemWithArtworkData(String mediaId) { MediaMetadata.Builder mediaMetadataBuilder = new MediaMetadata.Builder() - .setFolderType(MediaMetadata.FOLDER_TYPE_TITLES) .setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST) + .setIsBrowsable(false) .setIsPlayable(true); try { byte[] artworkData = @@ -107,8 +107,8 @@ public final class MediaTestUtils { public static MediaMetadata createMediaMetadata() { return new MediaMetadata.Builder() - .setFolderType(MediaMetadata.FOLDER_TYPE_NONE) - .setIsPlayable(false) + .setIsBrowsable(false) + .setIsPlayable(true) .setTitle(METADATA_TITLE) .setSubtitle(METADATA_SUBTITLE) .setDescription(METADATA_DESCRIPTION) @@ -120,8 +120,8 @@ public final class MediaTestUtils { public static MediaMetadata createMediaMetadataWithArtworkData() { MediaMetadata.Builder mediaMetadataBuilder = new MediaMetadata.Builder() - .setFolderType(MediaMetadata.FOLDER_TYPE_NONE) - .setIsPlayable(false) + .setIsBrowsable(false) + .setIsPlayable(true) .setTitle(METADATA_TITLE) .setSubtitle(METADATA_SUBTITLE) .setDescription(METADATA_DESCRIPTION) 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 d460bee20b..f5a2da634e 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 @@ -102,10 +102,7 @@ public class MockMediaLibraryService extends MediaLibraryService { new MediaItem.Builder() .setMediaId(ROOT_ID) .setMediaMetadata( - new MediaMetadata.Builder() - .setFolderType(MediaMetadata.FOLDER_TYPE_MIXED) - .setIsPlayable(false) - .build()) + new MediaMetadata.Builder().setIsBrowsable(true).setIsPlayable(false).build()) .build(); public static final LibraryParams ROOT_PARAMS = new LibraryParams.Builder().setExtras(ROOT_EXTRAS).build(); @@ -228,10 +225,7 @@ public class MockMediaLibraryService extends MediaLibraryService { new MediaItem.Builder() .setMediaId(customLibraryRoot) .setMediaMetadata( - new MediaMetadata.Builder() - .setFolderType(MediaMetadata.FOLDER_TYPE_ALBUMS) - .setIsPlayable(false) - .build()) + new MediaMetadata.Builder().setIsBrowsable(true).setIsPlayable(false).build()) .build(); } if (params != null) { @@ -243,10 +237,7 @@ public class MockMediaLibraryService extends MediaLibraryService { new MediaItem.Builder() .setMediaId(ROOT_ID_SUPPORTS_BROWSABLE_CHILDREN_ONLY) .setMediaMetadata( - new MediaMetadata.Builder() - .setFolderType(MediaMetadata.FOLDER_TYPE_MIXED) - .setIsPlayable(false) - .build()) + new MediaMetadata.Builder().setIsBrowsable(true).setIsPlayable(false).build()) .build(); } } @@ -478,7 +469,7 @@ public class MockMediaLibraryService extends MediaLibraryService { private MediaItem createBrowsableMediaItem(String mediaId) { MediaMetadata mediaMetadata = new MediaMetadata.Builder() - .setFolderType(MediaMetadata.FOLDER_TYPE_MIXED) + .setIsBrowsable(true) .setIsPlayable(false) .setArtworkData(getArtworkData(), MediaMetadata.PICTURE_TYPE_FRONT_COVER) .build(); @@ -501,7 +492,7 @@ public class MockMediaLibraryService extends MediaLibraryService { extras.putInt(EXTRAS_KEY_COMPLETION_STATUS, EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED); MediaMetadata mediaMetadata = new MediaMetadata.Builder() - .setFolderType(MediaMetadata.FOLDER_TYPE_NONE) + .setIsBrowsable(false) .setIsPlayable(true) .setExtras(extras) .build();