Don't advertise commands that are not available
When calling `MediaController.getCommandButtonForMediaItem(MediaItem)` command buttons with custom commands that are not available shouldn't be advertised to the controller when connected to a Media3 session. In contrast, when connected to a legacy session, available commands are not enforced when advertising commands. Similarly, when sending a custom commands that is referenced by a command button for media items, sending is permitted without the command being available. This is required because available commands match to custom actions in `PlaybackStateCompat` of the legacy session. Adding commands for media items to custom action of the `PlaybackStateCompat` would interfere with other use cases. Issue: androidx/media#1474 #cherrypick PiperOrigin-RevId: 683717723
This commit is contained in:
parent
546d7da2f2
commit
c4ff07e229
@ -43,6 +43,7 @@ import com.google.common.util.concurrent.SettableFuture;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||||
|
|
||||||
/** Implementation of MediaBrowser with the {@link MediaBrowserCompat} for legacy support. */
|
/** Implementation of MediaBrowser with the {@link MediaBrowserCompat} for legacy support. */
|
||||||
@ -93,8 +94,21 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ImmutableMap<String, CommandButton> getCommandButtonsForMediaItemsMap() {
|
public ImmutableList<CommandButton> getCommandButtonsForMediaItem(MediaItem mediaItem) {
|
||||||
return commandButtonsForMediaItems;
|
// Do not filter by available commands. When connected to a legacy session, the available
|
||||||
|
// session commands are read from the custom actions in PlaybackStateCompat (see
|
||||||
|
// LegacyConversion.convertToSessionCommands). Filtering by these commands would force a
|
||||||
|
// legacy session to put all commands for media items into the playback state as custom commands
|
||||||
|
// which would interfere with the custom commands set for media controls.
|
||||||
|
ImmutableList<String> supportedActions = mediaItem.mediaMetadata.supportedCommands;
|
||||||
|
ImmutableList.Builder<CommandButton> commandButtonsForMediaItem = new ImmutableList.Builder<>();
|
||||||
|
for (int i = 0; i < supportedActions.size(); i++) {
|
||||||
|
CommandButton commandButton = commandButtonsForMediaItems.get(supportedActions.get(i));
|
||||||
|
if (commandButton != null && commandButton.sessionCommand != null) {
|
||||||
|
commandButtonsForMediaItem.add(commandButton);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return commandButtonsForMediaItem.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -307,7 +321,9 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
|||||||
@Override
|
@Override
|
||||||
public ListenableFuture<SessionResult> sendCustomCommand(SessionCommand command, Bundle args) {
|
public ListenableFuture<SessionResult> sendCustomCommand(SessionCommand command, Bundle args) {
|
||||||
MediaBrowserCompat browserCompat = getBrowserCompat();
|
MediaBrowserCompat browserCompat = getBrowserCompat();
|
||||||
if (browserCompat != null && instance.isSessionCommandAvailable(command)) {
|
if (browserCompat != null
|
||||||
|
&& (instance.isSessionCommandAvailable(command)
|
||||||
|
|| isContainedInCommandButtonsForMediaItems(command))) {
|
||||||
SettableFuture<SessionResult> settable = SettableFuture.create();
|
SettableFuture<SessionResult> settable = SettableFuture.create();
|
||||||
browserCompat.sendCustomAction(
|
browserCompat.sendCustomAction(
|
||||||
command.customAction,
|
command.customAction,
|
||||||
@ -330,7 +346,20 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
|||||||
});
|
});
|
||||||
return settable;
|
return settable;
|
||||||
}
|
}
|
||||||
return super.sendCustomCommand(command, args);
|
return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_ERROR_PERMISSION_DENIED));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using this method as a proxy whether an browser is allowed to send a custom action can be
|
||||||
|
// justified because a MediaBrowserCompat can declare the custom browse actions in onGetRoot()
|
||||||
|
// specifically for each browser that connects. This is different to Media3 where the command
|
||||||
|
// buttons for media items are declared on the session level, and are constraint by the available
|
||||||
|
// session commands granted individually to a controller/browser in onConnect.
|
||||||
|
private boolean isContainedInCommandButtonsForMediaItems(SessionCommand command) {
|
||||||
|
if (command.commandCode != SessionCommand.COMMAND_CODE_CUSTOM) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
CommandButton commandButton = commandButtonsForMediaItems.get(command.customAction);
|
||||||
|
return commandButton != null && Objects.equals(commandButton.sessionCommand, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MediaBrowserCompat getBrowserCompat(LibraryParams extras) {
|
private MediaBrowserCompat getBrowserCompat(LibraryParams extras) {
|
||||||
|
@ -62,7 +62,6 @@ import androidx.media3.common.util.Util;
|
|||||||
import androidx.media3.datasource.DataSourceBitmapLoader;
|
import androidx.media3.datasource.DataSourceBitmapLoader;
|
||||||
import androidx.media3.session.legacy.MediaBrowserCompat;
|
import androidx.media3.session.legacy.MediaBrowserCompat;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
@ -674,16 +673,7 @@ public class MediaController implements Player {
|
|||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public final ImmutableList<CommandButton> getCommandButtonsForMediaItem(MediaItem mediaItem) {
|
public final ImmutableList<CommandButton> getCommandButtonsForMediaItem(MediaItem mediaItem) {
|
||||||
ImmutableMap<String, CommandButton> buttonMap = impl.getCommandButtonsForMediaItemsMap();
|
return impl.getCommandButtonsForMediaItem(mediaItem);
|
||||||
ImmutableList<String> supportedActions = mediaItem.mediaMetadata.supportedCommands;
|
|
||||||
ImmutableList.Builder<CommandButton> commandButtonsForMediaItem = new ImmutableList.Builder<>();
|
|
||||||
for (int i = 0; i < supportedActions.size(); i++) {
|
|
||||||
CommandButton commandButton = buttonMap.get(supportedActions.get(i));
|
|
||||||
if (commandButton != null) {
|
|
||||||
commandButtonsForMediaItem.add(commandButton);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return commandButtonsForMediaItem.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -2174,7 +2164,7 @@ public class MediaController implements Player {
|
|||||||
|
|
||||||
ImmutableList<CommandButton> getCustomLayout();
|
ImmutableList<CommandButton> getCustomLayout();
|
||||||
|
|
||||||
ImmutableMap<String, CommandButton> getCommandButtonsForMediaItemsMap();
|
ImmutableList<CommandButton> getCommandButtonsForMediaItem(MediaItem mediaItem);
|
||||||
|
|
||||||
Bundle getSessionExtras();
|
Bundle getSessionExtras();
|
||||||
|
|
||||||
|
@ -745,8 +745,19 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ImmutableMap<String, CommandButton> getCommandButtonsForMediaItemsMap() {
|
public ImmutableList<CommandButton> getCommandButtonsForMediaItem(MediaItem mediaItem) {
|
||||||
return commandButtonsForMediaItemsMap;
|
ImmutableList<String> supportedActions = mediaItem.mediaMetadata.supportedCommands;
|
||||||
|
SessionCommands availableSessionCommands = getAvailableSessionCommands();
|
||||||
|
ImmutableList.Builder<CommandButton> commandButtonsForMediaItem = new ImmutableList.Builder<>();
|
||||||
|
for (int i = 0; i < supportedActions.size(); i++) {
|
||||||
|
CommandButton commandButton = commandButtonsForMediaItemsMap.get(supportedActions.get(i));
|
||||||
|
if (commandButton != null
|
||||||
|
&& commandButton.sessionCommand != null
|
||||||
|
&& availableSessionCommands.contains(commandButton.sessionCommand)) {
|
||||||
|
commandButtonsForMediaItem.add(commandButton);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return commandButtonsForMediaItem.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -76,7 +76,6 @@ import androidx.media3.session.legacy.PlaybackStateCompat;
|
|||||||
import androidx.media3.session.legacy.RatingCompat;
|
import androidx.media3.session.legacy.RatingCompat;
|
||||||
import androidx.media3.session.legacy.VolumeProviderCompat;
|
import androidx.media3.session.legacy.VolumeProviderCompat;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.SettableFuture;
|
import com.google.common.util.concurrent.SettableFuture;
|
||||||
@ -102,6 +101,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
|||||||
private final ListenerSet<Listener> listeners;
|
private final ListenerSet<Listener> listeners;
|
||||||
private final ControllerCompatCallback controllerCompatCallback;
|
private final ControllerCompatCallback controllerCompatCallback;
|
||||||
private final BitmapLoader bitmapLoader;
|
private final BitmapLoader bitmapLoader;
|
||||||
|
private final ImmutableList<CommandButton> commandButtonsForMediaItems;
|
||||||
|
|
||||||
@Nullable private MediaControllerCompat controllerCompat;
|
@Nullable private MediaControllerCompat controllerCompat;
|
||||||
@Nullable private MediaBrowserCompat browserCompat;
|
@Nullable private MediaBrowserCompat browserCompat;
|
||||||
@ -137,6 +137,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
|||||||
this.bitmapLoader = bitmapLoader;
|
this.bitmapLoader = bitmapLoader;
|
||||||
currentPositionMs = C.TIME_UNSET;
|
currentPositionMs = C.TIME_UNSET;
|
||||||
lastSetPlayWhenReadyCalledTimeMs = C.TIME_UNSET;
|
lastSetPlayWhenReadyCalledTimeMs = C.TIME_UNSET;
|
||||||
|
// Always empty. Only supported for a MediaBrowser connected to a MediaBrowserServiceCompat.
|
||||||
|
commandButtonsForMediaItems = ImmutableList.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ MediaController getInstance() {
|
/* package */ MediaController getInstance() {
|
||||||
@ -410,6 +412,11 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
|||||||
controllerCompat.getTransportControls().fastForward();
|
controllerCompat.getTransportControls().fastForward();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableList<CommandButton> getCommandButtonsForMediaItem(MediaItem mediaItem) {
|
||||||
|
return commandButtonsForMediaItems;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public PendingIntent getSessionActivity() {
|
public PendingIntent getSessionActivity() {
|
||||||
@ -421,11 +428,6 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
|||||||
return controllerInfo.customLayout;
|
return controllerInfo.customLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public ImmutableMap<String, CommandButton> getCommandButtonsForMediaItemsMap() {
|
|
||||||
return ImmutableMap.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Bundle getSessionExtras() {
|
public Bundle getSessionExtras() {
|
||||||
return controllerInfo.sessionExtras;
|
return controllerInfo.sessionExtras;
|
||||||
|
@ -23,6 +23,8 @@ public class MediaSessionConstants {
|
|||||||
public static final String TEST_GET_CUSTOM_LAYOUT = "testGetCustomLayout";
|
public static final String TEST_GET_CUSTOM_LAYOUT = "testGetCustomLayout";
|
||||||
public static final String TEST_GET_COMMAND_BUTTONS_FOR_MEDIA_ITEMS =
|
public static final String TEST_GET_COMMAND_BUTTONS_FOR_MEDIA_ITEMS =
|
||||||
"testGetCommandButtonsForMediaItems";
|
"testGetCommandButtonsForMediaItems";
|
||||||
|
public static final String TEST_GET_COMMAND_BUTTONS_FOR_MEDIA_ITEMS_COMMANDS_NOT_AVAILABLE =
|
||||||
|
"testGetCommandButtonsForMediaItemsCommandsNotAvailable";
|
||||||
public static final String TEST_WITH_CUSTOM_COMMANDS = "testWithCustomCommands";
|
public static final String TEST_WITH_CUSTOM_COMMANDS = "testWithCustomCommands";
|
||||||
public static final String TEST_CONTROLLER_LISTENER_SESSION_REJECTS = "connection_sessionRejects";
|
public static final String TEST_CONTROLLER_LISTENER_SESSION_REJECTS = "connection_sessionRejects";
|
||||||
public static final String TEST_IS_SESSION_COMMAND_AVAILABLE = "testIsSessionCommandAvailable";
|
public static final String TEST_IS_SESSION_COMMAND_AVAILABLE = "testIsSessionCommandAvailable";
|
||||||
|
@ -220,8 +220,20 @@ public class MediaBrowserListenerWithMediaBrowserServiceCompatTest {
|
|||||||
@Test
|
@Test
|
||||||
public void sendCustomCommandWithMediaItem_mediaItemIdConvertedCorrectly() throws Exception {
|
public void sendCustomCommandWithMediaItem_mediaItemIdConvertedCorrectly() throws Exception {
|
||||||
remoteService.setProxyForTest(TEST_MEDIA_ITEMS_WITH_BROWSE_ACTIONS);
|
remoteService.setProxyForTest(TEST_MEDIA_ITEMS_WITH_BROWSE_ACTIONS);
|
||||||
MediaBrowser mediaBrowser = createBrowser(/* listener= */ null);
|
MediaBrowser mediaBrowser =
|
||||||
|
createBrowser(
|
||||||
|
/* connectionHints= */ Bundle.EMPTY,
|
||||||
|
/* maxCommandsForMediaItems= */ 2,
|
||||||
|
/* listener= */ null);
|
||||||
MediaItem mediaItem = new MediaItem.Builder().setMediaId("mediaIdFromCommand").build();
|
MediaItem mediaItem = new MediaItem.Builder().setMediaId("mediaIdFromCommand").build();
|
||||||
|
// When connected to a legacy browser service, the library root needs to be requested
|
||||||
|
// before media item commands are available.
|
||||||
|
LibraryResult<MediaItem> libraryRootResult =
|
||||||
|
threadTestRule
|
||||||
|
.getHandler()
|
||||||
|
.postAndSync(() -> mediaBrowser.getLibraryRoot(new LibraryParams.Builder().build()))
|
||||||
|
.get(TIMEOUT_MS, MILLISECONDS);
|
||||||
|
assertThat(libraryRootResult.resultCode).isEqualTo(RESULT_SUCCESS);
|
||||||
|
|
||||||
SessionResult sessionResult =
|
SessionResult sessionResult =
|
||||||
threadTestRule
|
threadTestRule
|
||||||
@ -232,12 +244,45 @@ public class MediaBrowserListenerWithMediaBrowserServiceCompatTest {
|
|||||||
new SessionCommand(MediaBrowserConstants.COMMAND_RADIO, Bundle.EMPTY),
|
new SessionCommand(MediaBrowserConstants.COMMAND_RADIO, Bundle.EMPTY),
|
||||||
mediaItem,
|
mediaItem,
|
||||||
/* args= */ Bundle.EMPTY))
|
/* args= */ Bundle.EMPTY))
|
||||||
.get();
|
.get(TIMEOUT_MS, MILLISECONDS);
|
||||||
|
|
||||||
assertThat(sessionResult.extras.getString(MediaConstants.EXTRA_KEY_MEDIA_ID))
|
assertThat(sessionResult.extras.getString(MediaConstants.EXTRA_KEY_MEDIA_ID))
|
||||||
.isEqualTo("mediaIdFromCommand");
|
.isEqualTo("mediaIdFromCommand");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sendCustomCommandWithMediaItem_commandButtonNotAvailable_permissionDenied()
|
||||||
|
throws Exception {
|
||||||
|
remoteService.setProxyForTest(TEST_MEDIA_ITEMS_WITH_BROWSE_ACTIONS);
|
||||||
|
MediaBrowser mediaBrowser =
|
||||||
|
createBrowser(
|
||||||
|
/* connectionHints= */ Bundle.EMPTY,
|
||||||
|
/* maxCommandsForMediaItems= */ 0,
|
||||||
|
/* listener= */ null);
|
||||||
|
MediaItem mediaItem = new MediaItem.Builder().setMediaId("mediaIdFromCommand").build();
|
||||||
|
// When connected to a legacy browser service, the library root needs to be requested
|
||||||
|
// before media item commands are available.
|
||||||
|
LibraryResult<MediaItem> libraryRootResult =
|
||||||
|
threadTestRule
|
||||||
|
.getHandler()
|
||||||
|
.postAndSync(() -> mediaBrowser.getLibraryRoot(new LibraryParams.Builder().build()))
|
||||||
|
.get(TIMEOUT_MS, MILLISECONDS);
|
||||||
|
assertThat(libraryRootResult.resultCode).isEqualTo(RESULT_SUCCESS);
|
||||||
|
|
||||||
|
SessionResult sessionResult =
|
||||||
|
threadTestRule
|
||||||
|
.getHandler()
|
||||||
|
.postAndSync(
|
||||||
|
() ->
|
||||||
|
mediaBrowser.sendCustomCommand(
|
||||||
|
new SessionCommand(MediaBrowserConstants.COMMAND_RADIO, Bundle.EMPTY),
|
||||||
|
mediaItem,
|
||||||
|
/* args= */ Bundle.EMPTY))
|
||||||
|
.get(TIMEOUT_MS, MILLISECONDS);
|
||||||
|
|
||||||
|
assertThat(sessionResult.resultCode).isEqualTo(SessionResult.RESULT_ERROR_PERMISSION_DENIED);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onChildrenChanged_subscribeAndUnsubscribe() throws Exception {
|
public void onChildrenChanged_subscribeAndUnsubscribe() throws Exception {
|
||||||
String testParentId = "testOnChildrenChanged";
|
String testParentId = "testOnChildrenChanged";
|
||||||
|
@ -22,6 +22,7 @@ import static androidx.media3.test.session.common.CommonConstants.DEFAULT_TEST_N
|
|||||||
import static androidx.media3.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
|
import static androidx.media3.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
|
||||||
import static androidx.media3.test.session.common.MediaSessionConstants.KEY_AVAILABLE_SESSION_COMMANDS;
|
import static androidx.media3.test.session.common.MediaSessionConstants.KEY_AVAILABLE_SESSION_COMMANDS;
|
||||||
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_GET_COMMAND_BUTTONS_FOR_MEDIA_ITEMS;
|
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_GET_COMMAND_BUTTONS_FOR_MEDIA_ITEMS;
|
||||||
|
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_GET_COMMAND_BUTTONS_FOR_MEDIA_ITEMS_COMMANDS_NOT_AVAILABLE;
|
||||||
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_GET_CUSTOM_LAYOUT;
|
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_GET_CUSTOM_LAYOUT;
|
||||||
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_GET_SESSION_ACTIVITY;
|
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_GET_SESSION_ACTIVITY;
|
||||||
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_IS_SESSION_COMMAND_AVAILABLE;
|
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_IS_SESSION_COMMAND_AVAILABLE;
|
||||||
@ -68,10 +69,8 @@ import androidx.test.core.app.ApplicationProvider;
|
|||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import androidx.test.filters.LargeTest;
|
import androidx.test.filters.LargeTest;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
@ -554,7 +553,8 @@ public class MediaControllerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void getCommandButtonsForMediaItem() throws Exception {
|
public void getCommandButtonsForMediaItem() throws Exception {
|
||||||
RemoteMediaSession session =
|
RemoteMediaSession session =
|
||||||
createRemoteMediaSession(TEST_GET_COMMAND_BUTTONS_FOR_MEDIA_ITEMS, /* tokenExtras= */ null);
|
createRemoteMediaSession(
|
||||||
|
TEST_GET_COMMAND_BUTTONS_FOR_MEDIA_ITEMS, /* tokenExtras= */ Bundle.EMPTY);
|
||||||
CommandButton playlistAddButton =
|
CommandButton playlistAddButton =
|
||||||
new CommandButton.Builder(CommandButton.ICON_PLAYLIST_ADD)
|
new CommandButton.Builder(CommandButton.ICON_PLAYLIST_ADD)
|
||||||
.setSessionCommand(
|
.setSessionCommand(
|
||||||
@ -588,10 +588,41 @@ public class MediaControllerTest {
|
|||||||
session.cleanUp();
|
session.cleanUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getCommandButtonsForMediaItem_availableCommandsNotGranted_commandButtonsEmpty()
|
||||||
|
throws Exception {
|
||||||
|
RemoteMediaSession session =
|
||||||
|
createRemoteMediaSession(
|
||||||
|
TEST_GET_COMMAND_BUTTONS_FOR_MEDIA_ITEMS_COMMANDS_NOT_AVAILABLE,
|
||||||
|
/* tokenExtras= */ Bundle.EMPTY);
|
||||||
|
MediaItem mediaItem =
|
||||||
|
new MediaItem.Builder()
|
||||||
|
.setMediaId("mediaId")
|
||||||
|
.setMediaMetadata(
|
||||||
|
new MediaMetadata.Builder()
|
||||||
|
.setSupportedCommands(
|
||||||
|
ImmutableList.of(
|
||||||
|
MediaBrowserConstants.COMMAND_PLAYLIST_ADD,
|
||||||
|
MediaBrowserConstants.COMMAND_RADIO,
|
||||||
|
"invalid"))
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
MediaController controller = controllerTestRule.createController(session.getToken());
|
||||||
|
|
||||||
|
ImmutableList<CommandButton> commandButtons =
|
||||||
|
threadTestRule
|
||||||
|
.getHandler()
|
||||||
|
.postAndSync(() -> controller.getCommandButtonsForMediaItem(mediaItem));
|
||||||
|
|
||||||
|
assertThat(commandButtons).isEmpty();
|
||||||
|
session.cleanUp();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void sendCustomCommandForMediaItem() throws Exception {
|
public void sendCustomCommandForMediaItem() throws Exception {
|
||||||
RemoteMediaSession session =
|
RemoteMediaSession session =
|
||||||
createRemoteMediaSession(TEST_GET_COMMAND_BUTTONS_FOR_MEDIA_ITEMS, /* tokenExtras= */ null);
|
createRemoteMediaSession(
|
||||||
|
TEST_GET_COMMAND_BUTTONS_FOR_MEDIA_ITEMS, /* tokenExtras= */ Bundle.EMPTY);
|
||||||
MediaItem mediaItem =
|
MediaItem mediaItem =
|
||||||
new MediaItem.Builder()
|
new MediaItem.Builder()
|
||||||
.setMediaId("mediaId-1")
|
.setMediaId("mediaId-1")
|
||||||
@ -601,11 +632,9 @@ public class MediaControllerTest {
|
|||||||
ImmutableList.of(MediaBrowserConstants.COMMAND_PLAYLIST_ADD))
|
ImmutableList.of(MediaBrowserConstants.COMMAND_PLAYLIST_ADD))
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
CountDownLatch latch = new CountDownLatch(/* count= */ 1);
|
|
||||||
AtomicReference<SessionResult> sessionResultRef = new AtomicReference<>();
|
|
||||||
MediaController controller = controllerTestRule.createController(session.getToken());
|
MediaController controller = controllerTestRule.createController(session.getToken());
|
||||||
|
|
||||||
Futures.addCallback(
|
SessionResult sessionResult =
|
||||||
threadTestRule
|
threadTestRule
|
||||||
.getHandler()
|
.getHandler()
|
||||||
.postAndSync(
|
.postAndSync(
|
||||||
@ -614,29 +643,48 @@ public class MediaControllerTest {
|
|||||||
controller.getCommandButtonsForMediaItem(mediaItem).get(0);
|
controller.getCommandButtonsForMediaItem(mediaItem).get(0);
|
||||||
return controller.sendCustomCommand(
|
return controller.sendCustomCommand(
|
||||||
commandButton.sessionCommand, mediaItem, Bundle.EMPTY);
|
commandButton.sessionCommand, mediaItem, Bundle.EMPTY);
|
||||||
}),
|
})
|
||||||
new FutureCallback<SessionResult>() {
|
.get(TIMEOUT_MS, MILLISECONDS);
|
||||||
@Override
|
|
||||||
public void onSuccess(SessionResult result) {
|
|
||||||
sessionResultRef.set(result);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
assertThat(sessionResult.resultCode).isEqualTo(SessionResult.RESULT_SUCCESS);
|
||||||
public void onFailure(Throwable t) {
|
assertThat(sessionResult.extras.getString(MediaConstants.EXTRA_KEY_MEDIA_ID))
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
MoreExecutors.directExecutor());
|
|
||||||
|
|
||||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
|
||||||
assertThat(sessionResultRef.get()).isNotNull();
|
|
||||||
assertThat(sessionResultRef.get().resultCode).isEqualTo(SessionResult.RESULT_SUCCESS);
|
|
||||||
assertThat(sessionResultRef.get().extras.getString(MediaConstants.EXTRA_KEY_MEDIA_ID))
|
|
||||||
.isEqualTo("mediaId-1");
|
.isEqualTo("mediaId-1");
|
||||||
session.cleanUp();
|
session.cleanUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sendCustomCommandForMediaItem_availableCommandsNotGranted_permissionDenied()
|
||||||
|
throws Exception {
|
||||||
|
RemoteMediaSession session =
|
||||||
|
createRemoteMediaSession(
|
||||||
|
TEST_GET_COMMAND_BUTTONS_FOR_MEDIA_ITEMS_COMMANDS_NOT_AVAILABLE,
|
||||||
|
/* tokenExtras= */ Bundle.EMPTY);
|
||||||
|
SessionCommand playlistAddSessionCommand =
|
||||||
|
new SessionCommand(MediaBrowserConstants.COMMAND_PLAYLIST_ADD, /* extras= */ Bundle.EMPTY);
|
||||||
|
MediaItem mediaItem =
|
||||||
|
new MediaItem.Builder()
|
||||||
|
.setMediaId("mediaId-1")
|
||||||
|
.setMediaMetadata(
|
||||||
|
new MediaMetadata.Builder()
|
||||||
|
.setSupportedCommands(
|
||||||
|
ImmutableList.of(MediaBrowserConstants.COMMAND_PLAYLIST_ADD))
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
MediaController controller = controllerTestRule.createController(session.getToken());
|
||||||
|
|
||||||
|
SessionResult sessionResult =
|
||||||
|
threadTestRule
|
||||||
|
.getHandler()
|
||||||
|
.postAndSync(
|
||||||
|
() ->
|
||||||
|
controller.sendCustomCommand(
|
||||||
|
playlistAddSessionCommand, mediaItem, Bundle.EMPTY))
|
||||||
|
.get(TIMEOUT_MS, MILLISECONDS);
|
||||||
|
|
||||||
|
assertThat(sessionResult.resultCode).isEqualTo(SessionResult.RESULT_ERROR_PERMISSION_DENIED);
|
||||||
|
session.cleanUp();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getSessionExtras_includedInConnectionStateWhenConnecting() throws Exception {
|
public void getSessionExtras_includedInConnectionStateWhenConnecting() throws Exception {
|
||||||
RemoteMediaSession session =
|
RemoteMediaSession session =
|
||||||
|
@ -64,6 +64,7 @@ import static androidx.media3.test.session.common.MediaSessionConstants.NOTIFICA
|
|||||||
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_COMMAND_GET_TRACKS;
|
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_COMMAND_GET_TRACKS;
|
||||||
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_CONTROLLER_LISTENER_SESSION_REJECTS;
|
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_CONTROLLER_LISTENER_SESSION_REJECTS;
|
||||||
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_GET_COMMAND_BUTTONS_FOR_MEDIA_ITEMS;
|
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_GET_COMMAND_BUTTONS_FOR_MEDIA_ITEMS;
|
||||||
|
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_GET_COMMAND_BUTTONS_FOR_MEDIA_ITEMS_COMMANDS_NOT_AVAILABLE;
|
||||||
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_GET_CUSTOM_LAYOUT;
|
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_GET_CUSTOM_LAYOUT;
|
||||||
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_GET_SESSION_ACTIVITY;
|
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_GET_SESSION_ACTIVITY;
|
||||||
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_IS_SESSION_COMMAND_AVAILABLE;
|
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_IS_SESSION_COMMAND_AVAILABLE;
|
||||||
@ -237,6 +238,7 @@ public class MediaSessionProviderService extends Service {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TEST_GET_COMMAND_BUTTONS_FOR_MEDIA_ITEMS:
|
case TEST_GET_COMMAND_BUTTONS_FOR_MEDIA_ITEMS:
|
||||||
|
case TEST_GET_COMMAND_BUTTONS_FOR_MEDIA_ITEMS_COMMANDS_NOT_AVAILABLE:
|
||||||
{
|
{
|
||||||
CommandButton playlistAddButton =
|
CommandButton playlistAddButton =
|
||||||
new CommandButton.Builder(CommandButton.ICON_PLAYLIST_ADD)
|
new CommandButton.Builder(CommandButton.ICON_PLAYLIST_ADD)
|
||||||
@ -256,6 +258,10 @@ public class MediaSessionProviderService extends Service {
|
|||||||
@Override
|
@Override
|
||||||
public MediaSession.ConnectionResult onConnect(
|
public MediaSession.ConnectionResult onConnect(
|
||||||
MediaSession session, ControllerInfo controller) {
|
MediaSession session, ControllerInfo controller) {
|
||||||
|
if (sessionId.equals(
|
||||||
|
TEST_GET_COMMAND_BUTTONS_FOR_MEDIA_ITEMS_COMMANDS_NOT_AVAILABLE)) {
|
||||||
|
return MediaSession.Callback.super.onConnect(session, controller);
|
||||||
|
}
|
||||||
return new MediaSession.ConnectionResult.AcceptedResultBuilder(session)
|
return new MediaSession.ConnectionResult.AcceptedResultBuilder(session)
|
||||||
.setAvailableSessionCommands(
|
.setAvailableSessionCommands(
|
||||||
new SessionCommands.Builder()
|
new SessionCommands.Builder()
|
||||||
|
@ -46,7 +46,6 @@ import android.support.v4.media.MediaBrowserCompat;
|
|||||||
import android.support.v4.media.MediaBrowserCompat.MediaItem;
|
import android.support.v4.media.MediaBrowserCompat.MediaItem;
|
||||||
import android.support.v4.media.MediaDescriptionCompat;
|
import android.support.v4.media.MediaDescriptionCompat;
|
||||||
import android.support.v4.media.session.MediaSessionCompat;
|
import android.support.v4.media.session.MediaSessionCompat;
|
||||||
import android.support.v4.media.session.MediaSessionCompat.Callback;
|
|
||||||
import android.support.v4.media.session.PlaybackStateCompat;
|
import android.support.v4.media.session.PlaybackStateCompat;
|
||||||
import androidx.annotation.GuardedBy;
|
import androidx.annotation.GuardedBy;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@ -89,7 +88,7 @@ public class MockMediaBrowserServiceCompat extends MediaBrowserServiceCompat {
|
|||||||
instance = this;
|
instance = this;
|
||||||
}
|
}
|
||||||
sessionCompat = new MediaSessionCompat(this, TAG);
|
sessionCompat = new MediaSessionCompat(this, TAG);
|
||||||
sessionCompat.setCallback(new Callback() {});
|
sessionCompat.setCallback(new MediaSessionCompat.Callback() {});
|
||||||
sessionCompat.setActive(true);
|
sessionCompat.setActive(true);
|
||||||
setSessionToken(sessionCompat.getSessionToken());
|
setSessionToken(sessionCompat.getSessionToken());
|
||||||
|
|
||||||
@ -401,27 +400,6 @@ public class MockMediaBrowserServiceCompat extends MediaBrowserServiceCompat {
|
|||||||
}
|
}
|
||||||
extras.putParcelableArrayList(
|
extras.putParcelableArrayList(
|
||||||
BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST, browseActionList);
|
BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST, browseActionList);
|
||||||
|
|
||||||
session.setPlaybackState(
|
|
||||||
new PlaybackStateCompat.Builder()
|
|
||||||
.setState(
|
|
||||||
PlaybackStateCompat.STATE_PLAYING,
|
|
||||||
/* position= */ 123L,
|
|
||||||
/* playbackSpeed= */ 1.0f)
|
|
||||||
.addCustomAction(
|
|
||||||
new PlaybackStateCompat.CustomAction.Builder(
|
|
||||||
MediaBrowserConstants.COMMAND_PLAYLIST_ADD,
|
|
||||||
"Add to playlist",
|
|
||||||
CommandButton.ICON_PLAYLIST_ADD)
|
|
||||||
.build())
|
|
||||||
.addCustomAction(
|
|
||||||
new PlaybackStateCompat.CustomAction.Builder(
|
|
||||||
MediaBrowserConstants.COMMAND_RADIO,
|
|
||||||
"Radio station",
|
|
||||||
CommandButton.ICON_RADIO)
|
|
||||||
.build())
|
|
||||||
.build());
|
|
||||||
|
|
||||||
return new BrowserRoot(ROOT_ID, extras);
|
return new BrowserRoot(ROOT_ID, extras);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user