diff --git a/constants.gradle b/constants.gradle index 12ce9f50e4..8dad0f3083 100644 --- a/constants.gradle +++ b/constants.gradle @@ -40,7 +40,7 @@ project.ext { androidxConstraintLayoutVersion = '2.0.4' androidxCoreVersion = '1.7.0' androidxFuturesVersion = '1.1.0' - androidxMediaVersion = '1.4.3' + androidxMediaVersion = '1.6.0' androidxMedia2Version = '1.2.0' androidxMultidexVersion = '2.0.1' androidxRecyclerViewVersion = '1.2.1' 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 ce5e4142d0..2db4a53fec 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaConstants.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaConstants.java @@ -15,18 +15,77 @@ */ package androidx.media3.session; +import android.app.PendingIntent; +import android.content.Intent; import android.os.Bundle; +import android.os.Parcelable; import android.support.v4.media.session.MediaControllerCompat; import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.PlaybackStateCompat; +import androidx.media3.common.MediaItem; +import androidx.media3.common.MediaMetadata; +import androidx.media3.common.util.UnstableApi; import androidx.media3.session.MediaLibraryService.LibraryParams; +import androidx.media3.session.MediaLibraryService.MediaLibrarySession; /** Constants that can be shared between media session and controller. */ public final class MediaConstants { /** - * Bundle key to indicate a preference that a region of space for the skip to next control should - * always be blocked out in the UI, even when the seek to next standard action is not supported. + * The legacy error code for expired authentication. + * + *

Use this error code to indicate an expired authentication when {@linkplain + * LibraryResult#ofError(int, LibraryParams) creating a library result} for an unsuccessful + * service call. + * + * @see PlaybackStateCompat#ERROR_CODE_AUTHENTICATION_EXPIRED + */ + public static final int ERROR_CODE_AUTHENTICATION_EXPIRED_COMPAT = 3; + + /** + * The extras key for the localized error resolution string. + * + *

Use this key to populate the extras bundle of the {@link LibraryParams} when {@linkplain + * LibraryResult#ofError(int, LibraryParams) creating a library result} for an unsuccessful + * service call. + */ + public static final String EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT = + androidx.media.utils.MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL; + + /** + * The extras key for the error resolution intent. + * + *

Use this key to populate the extras bundle of the {@link LibraryParams} when {@linkplain + * LibraryResult#ofError(int, LibraryParams) creating a library result} for an unsuccessful + * service call. + */ + public static final String EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT = + androidx.media.utils.MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT; + + /** + * {@link Bundle} key used to store a {@link PendingIntent}. When launched, the {@link + * PendingIntent} should allow users to resolve the current playback state error. + * + *

Applications must also set the error message and {@link + * #EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT} for cases in which the intent cannot be auto + * launched. + * + *

Use this key to populate the extras bundle of the {@link LibraryParams} when {@linkplain + * LibraryResult#ofError(int, LibraryParams) creating a library result} for an unsuccessful + * service call. Must be inserted {@linkplain Bundle#putParcelable(String, Parcelable) into the + * bundle as a parcelable}. + * + *

TYPE: {@link PendingIntent}. + */ + @UnstableApi + public static final String EXTRAS_KEY_ERROR_RESOLUTION_USING_CAR_APP_LIBRARY_INTENT_COMPAT = + androidx.media.utils.MediaConstants + .PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_USING_CAR_APP_LIBRARY_INTENT; + + /** + * {@link Bundle} key to indicate a preference that a region of space for the skip to next control + * should always be blocked out in the UI, even when the seek to next standard action is not + * supported. * *

This may be used when the session temporarily disallows {@link * androidx.media3.common.Player#COMMAND_SEEK_TO_NEXT} by design. @@ -39,12 +98,12 @@ public final class MediaConstants { * @see androidx.media3.common.Player#COMMAND_SEEK_TO_NEXT_MEDIA_ITEM */ public static final String EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT = - "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT"; + androidx.media.utils.MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT; /** - * Bundle key to indicate a preference that a region of space for the skip to previous control - * should always be blocked out in the UI, even when the seek to previous standard action is not - * supported. + * {@link Bundle} key to indicate a preference that a region of space for the skip to previous + * control should always be blocked out in the UI, even when the seek to previous standard action + * is not supported. * *

This may be used when the session temporarily disallows {@link * androidx.media3.common.Player#COMMAND_SEEK_TO_PREVIOUS} by design. @@ -57,43 +116,309 @@ public final class MediaConstants { * @see androidx.media3.common.Player#COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM */ public static final String EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV = - "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS"; + androidx.media.utils.MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV; /** - * The extras key for the localized error resolution string. + * {@link Bundle} key used in {@link MediaMetadata#extras} to indicate the playback completion + * status of the corresponding {@link MediaItem}. * - *

Use this key to populate the extras bundle of the {@link LibraryParams} when {@link - * LibraryResult#ofError(int, LibraryParams) creating a LibraryResult} for an unsuccessful service - * call. + *

TYPE: int. Possible values are separate constants. * - * @see - * androidx.media.utils.MediaConstants#PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL + * @see MediaMetadata.Builder#setExtras(Bundle) + * @see MediaMetadata#extras + * @see #EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED + * @see #EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED + * @see #EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED */ - public static final String EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT = - "android.media.extras.ERROR_RESOLUTION_ACTION_LABEL"; - /** - * The extras key for the error resolution intent. - * - *

Use this key to populate the extras bundle of the {@link LibraryParams} when {@link - * LibraryResult#ofError(int, LibraryParams) creating a LibraryResult} for an unsuccessful service - * call. - * - * @see - * androidx.media.utils.MediaConstants#PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT - */ - public static final String EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT = - "android.media.extras.ERROR_RESOLUTION_ACTION_INTENT"; + @UnstableApi + public static final String EXTRAS_KEY_COMPLETION_STATUS = + androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS; /** - * The legacy error code for expired authentication. + * {@link Bundle} value used in {@link MediaMetadata#extras} to indicate that the corresponding + * {@link MediaItem} has not been played by the user. * - *

Use this error code to indicate an expired authentication when {@link - * LibraryResult#ofError(int, LibraryParams) creating a LibraryResult} for an unsuccessful service - * call. - * - * @see PlaybackStateCompat#ERROR_CODE_AUTHENTICATION_EXPIRED + * @see MediaMetadata.Builder#setExtras(Bundle) + * @see MediaMetadata#extras + * @see #EXTRAS_KEY_COMPLETION_STATUS */ - public static final int ERROR_CODE_AUTHENTICATION_EXPIRED_COMPAT = 3; + @UnstableApi + public static final int EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED = + androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED; + + /** + * {@link Bundle} value used in {@link MediaMetadata#extras} to indicate that the corresponding + * {@link MediaItem} has been partially played by the user. + * + * @see MediaMetadata.Builder#setExtras(Bundle) + * @see MediaMetadata#extras + * @see #EXTRAS_KEY_COMPLETION_STATUS + */ + @UnstableApi + public static final int EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED = + androidx.media.utils.MediaConstants + .DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED; + + /** + * {@link Bundle} value used in {@link MediaMetadata#extras} to indicate that the corresponding + * {@link MediaItem} has been fully played by the user. + * + * @see MediaMetadata.Builder#setExtras(Bundle) + * @see MediaMetadata#extras + * @see #EXTRAS_KEY_COMPLETION_STATUS + */ + @UnstableApi + public static final int EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED = + androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED; + + /** + * {@link Bundle} key used in {@link MediaMetadata#extras} to indicate an amount of completion + * progress for the corresponding {@link MediaItem}. This extra augments {@link + * #EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED the partially played status} by indicating how + * much has been played by the user. + * + *

TYPE: double, a value between 0.0 and 1.0, inclusive. 0.0 indicates no completion progress + * (item is not started) and 1.0 indicates full completion progress (item is fully played). Values + * in between indicate partial progress (for example, 0.75 indicates the item is 75% complete). + * + * @see MediaMetadata.Builder#setExtras(Bundle) + * @see MediaMetadata#extras + */ + @UnstableApi + public static final String EXTRAS_KEY_COMPLETION_PERCENTAGE = + androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE; + + /** + * {@link Bundle} key used to indicate a preference about how playable instances of {@link + * MediaItem} are presented. + * + *

If exposed through {@link LibraryParams#extras} of the {@link LibraryResult} returned by + * {@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 + * browsable media item}, the preference applies to only the immediate playable children. It takes + * precedence over preferences received with {@link MediaBrowser#getLibraryRoot}. + * + *

TYPE: int. Possible values are separate constants. + * + * @see MediaBrowser#getLibraryRoot(LibraryParams) + * @see LibraryResult#params + * @see MediaMetadata.Builder#setExtras(Bundle) + * @see MediaMetadata#extras + * @see #EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM + * @see #EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM + */ + @UnstableApi + public static final String EXTRAS_KEY_CONTENT_STYLE_PLAYABLE = + androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE; + + /** + * {@link Bundle} key used to indicate a preference about how browsable instances of {@link + * MediaItem} are presented. + * + *

If exposed through {@link LibraryParams#extras} of the {@link LibraryResult} returned by + * {@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 + * browsable media item}, the preference applies to only the immediate browsable children. It + * takes precedence over preferences received with {@link + * MediaBrowser#getLibraryRoot(LibraryParams)}. + * + *

TYPE: int. Possible values are separate constants. + * + * @see MediaBrowser#getLibraryRoot(LibraryParams) + * @see LibraryResult#params + * @see MediaMetadata.Builder#setExtras(Bundle) + * @see MediaMetadata#extras + * @see #EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM + * @see #EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM + * @see #EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM + * @see #EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM + */ + @UnstableApi + public static final String EXTRAS_KEY_CONTENT_STYLE_BROWSABLE = + androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE; + + /** + * {@link Bundle} key used in {@link MediaMetadata#extras} to indicate a preference about how the + * corresponding {@link MediaItem} is presented. + * + *

This preference takes precedence over those expressed by {@link + * #EXTRAS_KEY_CONTENT_STYLE_PLAYABLE} and {@link #EXTRAS_KEY_CONTENT_STYLE_BROWSABLE}. + * + *

TYPE: int. Possible values are separate constants. + * + * @see MediaMetadata.Builder#setExtras(Bundle) + * @see MediaMetadata#extras + * @see #EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM + * @see #EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM + * @see #EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM + * @see #EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM + */ + @UnstableApi + public static final String EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM = + androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM; + + /** + * {@link Bundle} value used in {@link MediaMetadata#extras} to indicate a preference that certain + * instances of {@link MediaItem} should be presented as list items. + * + * @see MediaMetadata.Builder#setExtras(Bundle) + * @see MediaMetadata#extras + * @see #EXTRAS_KEY_CONTENT_STYLE_BROWSABLE + * @see #EXTRAS_KEY_CONTENT_STYLE_PLAYABLE + */ + @UnstableApi + public static final int EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM = + androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM; + + /** + * {@link Bundle} value used in {@link MediaMetadata#extras} to indicate a preference that certain + * instances of {@link MediaItem} should be presented as grid items. + * + * @see MediaMetadata.Builder#setExtras(Bundle) + * @see MediaMetadata#extras + * @see #EXTRAS_KEY_CONTENT_STYLE_BROWSABLE + * @see #EXTRAS_KEY_CONTENT_STYLE_PLAYABLE + */ + @UnstableApi + public static final int EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM = + androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM; + + /** + * {@link Bundle} value used in {@link MediaMetadata#extras} to indicate a preference that + * browsable instances of {@link MediaItem} should be presented as "category" list items. This + * means the items provide icons that render well when they do not fill all of + * the available area. + * + * @see MediaMetadata.Builder#setExtras(Bundle) + * @see MediaMetadata#extras + * @see #EXTRAS_KEY_CONTENT_STYLE_BROWSABLE + */ + @UnstableApi + public static final int EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM = + androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM; + + /** + * {@link Bundle} value used in {@link MediaMetadata#extras} to indicate a preference that + * browsable instances of {@link MediaItem} should be presented as "category" grid items. This + * means the items provide icons that render well when they do not fill all of + * the available area. + * + * @see MediaMetadata.Builder#setExtras(Bundle) + * @see MediaMetadata#extras + * @see #EXTRAS_KEY_CONTENT_STYLE_BROWSABLE + */ + @UnstableApi + public static final int EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM = + androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM; + + /** + * {@link Bundle} key used in {@link MediaMetadata#extras} to indicate that certain instances of + * {@link MediaItem} are related as a group, with a title that is specified through the bundle + * value. Items that are children of the same browsable node and have the same title are members + * of the same group. An app may present a group's items as a contiguous block and display the + * title alongside the group. + * + *

TYPE: String. Should be human readable and localized. + * + * @see MediaMetadata.Builder#setExtras(Bundle) + * @see MediaMetadata#extras + */ + @UnstableApi + public static final String EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE = + androidx.media.utils.MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE; + + /** + * {@link Bundle} key used in {@link MediaMetadata#extras} to indicate that the corresponding + * {@link MediaItem} has explicit content (that is, user discretion is advised when viewing or + * listening to this content). + * + *

TYPE: long (to enable, use value {@link #EXTRAS_VALUE_ATTRIBUTE_PRESENT}) + * + * @see MediaMetadata.Builder#setExtras(Bundle) + * @see MediaMetadata#extras + */ + @UnstableApi public static final String EXTRAS_KEY_IS_EXPLICIT = "android.media.IS_EXPLICIT"; + + /** + * {@link Bundle} key used in {@link MediaMetadata#extras} to indicate that the corresponding + * {@link MediaItem} is an advertisement. + * + *

TYPE: long (to enable, use value {@link #EXTRAS_VALUE_ATTRIBUTE_PRESENT}) + * + * @see MediaMetadata.Builder#setExtras(Bundle) + * @see MediaMetadata#extras + */ + @UnstableApi + public static final String EXTRAS_KEY_IS_ADVERTISEMENT = + androidx.media.utils.MediaConstants.METADATA_KEY_IS_ADVERTISEMENT; + + /** + * {@link Bundle} value used to indicate the presence of an attribute described by its + * corresponding key. + */ + @UnstableApi public static final long EXTRAS_VALUE_ATTRIBUTE_PRESENT = 1L; + + /** + * {@link Bundle} key used in {@link LibraryParams#extras} passed to {@link + * MediaLibrarySession.Callback#onGetLibraryRoot(MediaLibrarySession, MediaSession.ControllerInfo, + * LibraryParams)} to indicate the maximum number of children of the root node that can be + * supported by the {@link MediaBrowser}. Excess root children may be omitted or made less + * discoverable. + * + *

TYPE: int + * + * @see MediaLibrarySession.Callback#onGetLibraryRoot(MediaLibrarySession, + * MediaSession.ControllerInfo, LibraryParams) + * @see LibraryParams#extras + */ + @UnstableApi + public static final String EXTRAS_KEY_ROOT_CHILDREN_LIMIT = + androidx.media.utils.MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT; + + /** + * {@link Bundle} key used in {@link LibraryParams#extras} passed by the {@link MediaBrowser} as + * root hints to {@link MediaLibrarySession.Callback#onGetLibraryRoot(MediaLibrarySession, + * MediaSession.ControllerInfo, LibraryParams)} to indicate the recommended size, in pixels, for + * media art bitmaps. Much smaller images may not render well, and much larger images may cause + * inefficient resource consumption. + * + * @see MediaBrowser#getLibraryRoot(LibraryParams) + * @see MediaLibrarySession.Callback#onGetLibraryRoot(MediaLibrarySession, + * MediaSession.ControllerInfo, LibraryParams) + * @see LibraryParams#extras + */ + @UnstableApi + public static final String EXTRAS_KEY_MEDIA_ART_SIZE_PIXELS = + androidx.media.utils.MediaConstants.BROWSER_ROOT_HINTS_KEY_MEDIA_ART_SIZE_PIXELS; + + /** + * {@link Bundle} key used to indicate that the media app that provides the service supports + * showing a settings page. + * + *

Use this key to populate the {@link LibraryParams#extras} of the {@link LibraryResult} + * returned by {@link MediaLibrarySession.Callback#onGetLibraryRoot(MediaLibrarySession, + * MediaSession.ControllerInfo, LibraryParams)}. Use this key with {@link + * Bundle#putParcelable(String, Parcelable)} to put a {@link PendingIntent} that is created using + * {@code CarPendingIntent#getCarApp()}. + * + *

The {@link Intent} carried by the pending intent needs to have the component name set to a + * Car App Library + * service that needs to exist in the same application package as the media browser service. + * + *

TYPE: {@link PendingIntent}. + * + * @see MediaLibrarySession.Callback#onGetLibraryRoot(MediaLibrarySession, + * MediaSession.ControllerInfo, LibraryParams) + * @see LibraryParams#extras + */ + @UnstableApi + public static final String EXTRAS_KEY_APPLICATION_PREFERENCES_USING_CAR_APP_LIBRARY_INTENT = + androidx.media.utils.MediaConstants + .BROWSER_SERVICE_EXTRAS_KEY_APPLICATION_PREFERENCES_USING_CAR_APP_LIBRARY_INTENT; /* package */ static final String SESSION_COMMAND_ON_CAPTIONING_ENABLED_CHANGED = "androidx.media3.session.SESSION_COMMAND_ON_CAPTIONING_ENABLED_CHANGED"; diff --git a/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/MediaBrowserConstants.java b/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/MediaBrowserConstants.java index 99138d683d..65e7847199 100644 --- a/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/MediaBrowserConstants.java +++ b/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/MediaBrowserConstants.java @@ -26,6 +26,8 @@ public class MediaBrowserConstants { public static final String ROOT_ID = "rootId"; public static final Bundle ROOT_EXTRAS = new Bundle(); + public static final String ROOT_EXTRAS_KEY = "root_extras_key"; + public static final int ROOT_EXTRAS_VALUE = 4321; public static final String MEDIA_ID_GET_BROWSABLE_ITEM = "media_id_get_browsable_item"; public static final String MEDIA_ID_GET_PLAYABLE_ITEM = "media_id_get_playable_item"; @@ -71,7 +73,7 @@ public class MediaBrowserConstants { public static final String CUSTOM_ACTION_ASSERT_PARAMS = "assertParams"; static { - ROOT_EXTRAS.putString(ROOT_ID, ROOT_ID); + ROOT_EXTRAS.putInt(ROOT_EXTRAS_KEY, ROOT_EXTRAS_VALUE); CUSTOM_ACTION_EXTRAS.putString(CUSTOM_ACTION, CUSTOM_ACTION); diff --git a/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/MediaBrowserServiceCompatConstants.java b/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/MediaBrowserServiceCompatConstants.java index 698e996aaf..f864078f07 100644 --- a/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/MediaBrowserServiceCompatConstants.java +++ b/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/MediaBrowserServiceCompatConstants.java @@ -24,6 +24,8 @@ public class MediaBrowserServiceCompatConstants { public static final String TEST_CONNECT_REJECTED = "testConnect_rejected"; public static final String TEST_ON_CHILDREN_CHANGED_SUBSCRIBE_AND_UNSUBSCRIBE = "testOnChildrenChanged_subscribeAndUnsubscribe"; + public static final String TEST_GET_LIBRARY_ROOT = "getLibraryRoot_correctExtraKeyAndValue"; + public static final String TEST_GET_CHILDREN = "getChildren_correctMetadataExtras"; private MediaBrowserServiceCompatConstants() {} } 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 aadc857a3c..e6b31bd549 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 @@ -15,6 +15,8 @@ */ package androidx.media3.session; +import static androidx.media3.session.MediaConstants.EXTRAS_KEY_COMPLETION_STATUS; +import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED; import static androidx.media3.test.session.common.CommonConstants.METADATA_ARTWORK_URI; import static androidx.media3.test.session.common.CommonConstants.METADATA_DESCRIPTION; import static androidx.media3.test.session.common.CommonConstants.METADATA_EXTRA_KEY; @@ -38,6 +40,8 @@ import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_I import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_LONG_LIST; import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID_NO_CHILDREN; import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS; +import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS_KEY; +import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS_VALUE; import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_ID; import static androidx.media3.test.session.common.MediaBrowserConstants.SEARCH_QUERY; import static androidx.media3.test.session.common.MediaBrowserConstants.SEARCH_QUERY_EMPTY_RESULT; @@ -67,6 +71,7 @@ import androidx.media3.test.session.common.TestUtils; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.truth.os.BundleSubject; import androidx.test.filters.LargeTest; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; @@ -96,6 +101,11 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest }); connectAndWait(); assertThat(browserCompat.getRoot()).isEqualTo(ROOT_ID); + assertThat( + browserCompat + .getExtras() + .getInt(ROOT_EXTRAS_KEY, /* defaultValue= */ ROOT_EXTRAS_VALUE + 1)) + .isEqualTo(ROOT_EXTRAS_VALUE); // Note: Cannot use equals() here because browser compat's extra contains server version, // extra binder, and extra messenger. @@ -202,22 +212,18 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest @Test public void getChildren() throws InterruptedException { String testParentId = PARENT_ID; - connectAndWait(); CountDownLatch latch = new CountDownLatch(1); + List receivedChildren = new ArrayList<>(); + final String[] receivedParentId = new String[1]; + browserCompat.subscribe( testParentId, new SubscriptionCallback() { @Override public void onChildrenLoaded(String parentId, List children) { - assertThat(parentId).isEqualTo(testParentId); - assertThat(children).isNotNull(); - assertThat(children.size()).isEqualTo(GET_CHILDREN_RESULT.size()); - - // Compare the given results with originals. - for (int i = 0; i < children.size(); i++) { - assertThat(children.get(i).getMediaId()).isEqualTo(GET_CHILDREN_RESULT.get(i)); - } + receivedParentId[0] = parentId; + receivedChildren.addAll(children); latch.countDown(); } @@ -226,7 +232,23 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest assertWithMessage("").fail(); } }); + assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); + assertThat(receivedParentId[0]).isEqualTo(testParentId); + assertThat(receivedChildren).hasSize(GET_CHILDREN_RESULT.size()); + // Compare the given results with originals. + for (int i = 0; i < receivedChildren.size(); i++) { + MediaItem mediaItem = receivedChildren.get(i); + assertThat(mediaItem.getMediaId()).isEqualTo(GET_CHILDREN_RESULT.get(i)); + assertThat( + mediaItem + .getDescription() + .getExtras() + .getInt( + EXTRAS_KEY_COMPLETION_STATUS, + /* defaultValue= */ EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED + 1)) + .isEqualTo(EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED); + } } @Test 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 efac881994..45c7171ccb 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 @@ -17,12 +17,17 @@ package androidx.media3.session; import static androidx.media3.session.LibraryResult.RESULT_ERROR_BAD_VALUE; import static androidx.media3.session.LibraryResult.RESULT_SUCCESS; +import static androidx.media3.session.MediaConstants.EXTRAS_KEY_COMPLETION_STATUS; +import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED; import static androidx.media3.test.session.common.CommonConstants.MOCK_MEDIA3_LIBRARY_SERVICE; import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION_ASSERT_PARAMS; import static androidx.media3.test.session.common.MediaBrowserConstants.LONG_LIST_COUNT; import static androidx.media3.test.session.common.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED_EXTRAS; import static androidx.media3.test.session.common.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED_ITEM_COUNT; +import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID; import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS; +import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS_KEY; +import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS_VALUE; import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_ID; import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL; import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_ID_NOTIFY_CHILDREN_CHANGED_TO_ALL_WITH_NON_SUBSCRIBED_ID; @@ -98,6 +103,45 @@ public class MediaBrowserListenerTest extends MediaControllerListenerTest { assertThat(TestUtils.equals(ROOT_EXTRAS, result.params.extras)).isTrue(); } + @Test + public void getLibraryRoot_correctExtraKeyAndValue() throws Exception { + MediaBrowser browser = createBrowser(); + + LibraryResult resultForLibraryRoot = + threadTestRule + .getHandler() + .postAndSync(() -> browser.getLibraryRoot(new LibraryParams.Builder().build())) + .get(TIMEOUT_MS, MILLISECONDS); + + Bundle extras = resultForLibraryRoot.params.extras; + + assertThat(extras.getInt(ROOT_EXTRAS_KEY, /* defaultValue= */ ROOT_EXTRAS_VALUE + 1)) + .isEqualTo(ROOT_EXTRAS_VALUE); + } + + @Test + public void getChildren_correctMetadataExtras() throws Exception { + LibraryParams params = MediaTestUtils.createLibraryParams(); + MediaBrowser browser = createBrowser(); + + LibraryResult> libraryResult = + threadTestRule + .getHandler() + .postAndSync( + () -> browser.getChildren(PARENT_ID, /* page= */ 4, /* pageSize= */ 10, params)) + .get(TIMEOUT_MS, MILLISECONDS); + + assertThat(libraryResult.resultCode).isEqualTo(RESULT_SUCCESS); + assertThat(libraryResult.value).isNotEmpty(); + for (MediaItem mediaItem : libraryResult.value) { + int status = + mediaItem.mediaMetadata.extras.getInt( + EXTRAS_KEY_COMPLETION_STATUS, + /* defaultValue= */ EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED + 1); + assertThat(status).isEqualTo(EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED); + } + } + @Test public void getItem_browsable() throws Exception { String mediaId = MediaBrowserConstants.MEDIA_ID_GET_BROWSABLE_ITEM; @@ -144,14 +188,15 @@ public class MediaBrowserListenerTest extends MediaControllerListenerTest { } @Test - public void getChildren() throws Exception { - String parentId = MediaBrowserConstants.PARENT_ID; + public void getChildren_correctLibraryResultWithExtras() throws Exception { + String parentId = PARENT_ID; int page = 4; int pageSize = 10; LibraryParams params = MediaTestUtils.createLibraryParams(); MediaBrowser browser = createBrowser(); setExpectedLibraryParam(browser, params); + LibraryResult> result = threadTestRule .getHandler() @@ -159,7 +204,6 @@ public class MediaBrowserListenerTest extends MediaControllerListenerTest { .get(TIMEOUT_MS, MILLISECONDS); assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS); MediaTestUtils.assertLibraryParamsEquals(params, result.params); - MediaTestUtils.assertPaginatedListHasIds( result.value, MediaBrowserConstants.GET_CHILDREN_RESULT, page, pageSize); } diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserListenerWithMediaBrowserServiceCompatTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserListenerWithMediaBrowserServiceCompatTest.java index 166050154f..d9f8a0afd0 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserListenerWithMediaBrowserServiceCompatTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserListenerWithMediaBrowserServiceCompatTest.java @@ -16,8 +16,15 @@ package androidx.media3.session; import static androidx.media3.session.LibraryResult.RESULT_SUCCESS; +import static androidx.media3.session.MediaConstants.EXTRAS_KEY_COMPLETION_STATUS; +import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED; import static androidx.media3.test.session.common.CommonConstants.MOCK_MEDIA_BROWSER_SERVICE_COMPAT; +import static androidx.media3.test.session.common.MediaBrowserConstants.PARENT_ID; +import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS_KEY; +import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS_VALUE; import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_CONNECT_REJECTED; +import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_GET_CHILDREN; +import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_GET_LIBRARY_ROOT; import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_ON_CHILDREN_CHANGED_SUBSCRIBE_AND_UNSUBSCRIBE; import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS; import static com.google.common.truth.Truth.assertThat; @@ -25,13 +32,16 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.junit.Assert.assertThrows; import android.content.Context; +import android.os.Bundle; import androidx.annotation.Nullable; import androidx.media.MediaBrowserServiceCompat; +import androidx.media3.common.MediaItem; import androidx.media3.session.MediaLibraryService.LibraryParams; import androidx.media3.test.session.common.HandlerThreadTestRule; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; +import com.google.common.collect.ImmutableList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import org.junit.After; @@ -137,4 +147,56 @@ public class MediaBrowserListenerWithMediaBrowserServiceCompatTest { // Wait for some time. Exception will be thrown in the listener if error happens. Thread.sleep(TIMEOUT_MS); } + + @Test + public void getLibraryRoot_correctExtraKeyAndValue() throws Exception { + remoteService.setProxyForTest(TEST_GET_LIBRARY_ROOT); + MediaBrowser browser = createBrowser(/* listener= */ null); + + LibraryResult resultForLibraryRoot = + threadTestRule + .getHandler() + .postAndSync(() -> browser.getLibraryRoot(new LibraryParams.Builder().build())) + .get(TIMEOUT_MS, MILLISECONDS); + + Bundle extras = resultForLibraryRoot.params.extras; + assertThat(extras.getInt(ROOT_EXTRAS_KEY, ROOT_EXTRAS_VALUE + 1)).isEqualTo(ROOT_EXTRAS_VALUE); + } + + @Test + public void getChildren_correctMetadataExtras() throws Exception { + LibraryParams params = MediaTestUtils.createLibraryParams(); + remoteService.setProxyForTest(TEST_GET_CHILDREN); + MediaBrowser browser = createBrowser(/* listener= */ null); + + LibraryResult> libraryResult = + threadTestRule + .getHandler() + .postAndSync( + () -> browser.getChildren(PARENT_ID, /* page= */ 4, /* pageSize= */ 10, params)) + .get(TIMEOUT_MS, MILLISECONDS); + + assertThat(libraryResult.resultCode).isEqualTo(RESULT_SUCCESS); + assertThat(libraryResult.value).hasSize(MockMediaBrowserServiceCompat.MEDIA_ITEMS.size()); + for (int i = 0; i < libraryResult.value.size(); i++) { + int status = + libraryResult + .value + .get(i) + .mediaMetadata + .extras + .getInt( + EXTRAS_KEY_COMPLETION_STATUS, + /* defaultValue= */ EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED + 1); + int expectedStatus = + MockMediaBrowserServiceCompat.MEDIA_ITEMS + .get(i) + .getDescription() + .getExtras() + .getInt( + EXTRAS_KEY_COMPLETION_STATUS, + /* defaultValue= */ EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED + 1); + assertThat(status).isEqualTo(expectedStatus); + } + } } diff --git a/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaBrowserServiceCompat.java b/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaBrowserServiceCompat.java index 51f93f3c4a..65ce255a30 100644 --- a/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaBrowserServiceCompat.java +++ b/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaBrowserServiceCompat.java @@ -15,21 +15,33 @@ */ package androidx.media3.session; +import static androidx.media3.session.MediaConstants.EXTRAS_KEY_COMPLETION_STATUS; +import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED; +import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED; +import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED; import static androidx.media3.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME; +import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_EXTRAS; +import static androidx.media3.test.session.common.MediaBrowserConstants.ROOT_ID; import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_CONNECT_REJECTED; +import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_GET_CHILDREN; +import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_GET_LIBRARY_ROOT; import static androidx.media3.test.session.common.MediaBrowserServiceCompatConstants.TEST_ON_CHILDREN_CHANGED_SUBSCRIBE_AND_UNSUBSCRIBE; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; +import android.support.v4.media.MediaBrowserCompat; import android.support.v4.media.MediaBrowserCompat.MediaItem; +import android.support.v4.media.MediaDescriptionCompat; import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.MediaSessionCompat.Callback; import androidx.annotation.GuardedBy; import androidx.media.MediaBrowserServiceCompat; import androidx.media3.common.util.UnstableApi; import androidx.media3.test.session.common.IRemoteMediaBrowserServiceCompat; +import androidx.media3.test.session.common.MediaBrowserServiceCompatConstants; +import com.google.common.collect.ImmutableList; import java.lang.reflect.Method; import java.util.Collections; import java.util.List; @@ -38,6 +50,12 @@ import java.util.List; @UnstableApi public class MockMediaBrowserServiceCompat extends MediaBrowserServiceCompat { + /** + * Immutable list of media items sent to controllers for {@link + * MediaBrowserServiceCompatConstants#TEST_GET_CHILDREN}. + */ + public static final ImmutableList MEDIA_ITEMS = createMediaItems(); + private static final String TAG = "MockMBSCompat"; private static final Object lock = new Object(); @@ -226,6 +244,12 @@ public class MockMediaBrowserServiceCompat extends MediaBrowserServiceCompat { case TEST_ON_CHILDREN_CHANGED_SUBSCRIBE_AND_UNSUBSCRIBE: setProxyForTestOnChildrenChanged_subscribeAndUnsubscribe(); break; + case TEST_GET_LIBRARY_ROOT: + setProxyForTestGetLibraryRoot_correctExtraKeyAndValue(); + break; + case TEST_GET_CHILDREN: + setProxyForTestGetChildren_correctMetadataExtras(); + break; default: throw new IllegalArgumentException("Unknown testName: " + testName); } @@ -257,5 +281,54 @@ public class MockMediaBrowserServiceCompat extends MediaBrowserServiceCompat { } }); } + + private void setProxyForTestGetChildren_correctMetadataExtras() { + setMediaBrowserServiceProxy( + new MockMediaBrowserServiceCompat.Proxy() { + @Override + public void onLoadChildren(String parentId, Result> result) { + onLoadChildren(parentId, result, new Bundle()); + } + + @Override + public void onLoadChildren( + String parentId, Result> result, Bundle bundle) { + result.sendResult(MEDIA_ITEMS); + } + }); + } + + private void setProxyForTestGetLibraryRoot_correctExtraKeyAndValue() { + setMediaBrowserServiceProxy( + new MockMediaBrowserServiceCompat.Proxy() { + @Override + public BrowserRoot onGetRoot( + String clientPackageName, int clientUid, Bundle rootHints) { + return new BrowserRoot(ROOT_ID, ROOT_EXTRAS); + } + }); + } + } + + private static ImmutableList createMediaItems() { + int[] completionStates = + new int[] { + EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED, + EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED, + EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED + }; + ImmutableList.Builder builder = new ImmutableList.Builder<>(); + for (int i = 0; i < 3; i++) { + Bundle extras = new Bundle(); + extras.putInt(EXTRAS_KEY_COMPLETION_STATUS, completionStates[i]); + builder.add( + new MediaBrowserCompat.MediaItem( + new MediaDescriptionCompat.Builder() + .setMediaId("media-id-" + i) + .setExtras(extras) + .build(), + /* flags= */ 0)); + } + return builder.build(); } } 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 982e557cfe..0749077056 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 @@ -16,8 +16,10 @@ package androidx.media3.session; import static androidx.media3.common.util.Assertions.checkNotNull; +import static androidx.media3.session.MediaConstants.EXTRAS_KEY_COMPLETION_STATUS; import static androidx.media3.session.MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT; import static androidx.media3.session.MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT; +import static androidx.media3.session.MediaConstants.EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED; import static androidx.media3.session.MediaTestUtils.assertLibraryParamsEquals; import static androidx.media3.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME; import static androidx.media3.test.session.common.MediaBrowserConstants.CUSTOM_ACTION; @@ -63,7 +65,6 @@ import androidx.media3.common.MediaMetadata; import androidx.media3.common.util.Log; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; -import androidx.media3.session.MediaLibraryService.MediaLibrarySession; import androidx.media3.session.MediaSession.ControllerInfo; import androidx.media3.test.session.common.CommonConstants; import androidx.media3.test.session.common.TestHandler; @@ -425,10 +426,13 @@ public class MockMediaLibraryService extends MediaLibraryService { } private static MediaItem createPlayableMediaItem(String mediaId) { + Bundle extras = new Bundle(); + extras.putInt(EXTRAS_KEY_COMPLETION_STATUS, EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED); MediaMetadata mediaMetadata = new MediaMetadata.Builder() .setFolderType(MediaMetadata.FOLDER_TYPE_NONE) .setIsPlayable(true) + .setExtras(extras) .build(); return new MediaItem.Builder().setMediaId(mediaId).setMediaMetadata(mediaMetadata).build(); }