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:
bachinger 2024-10-08 12:11:49 -07:00 committed by Copybara-Service
parent 546d7da2f2
commit c4ff07e229
9 changed files with 185 additions and 74 deletions

View File

@ -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) {

View File

@ -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();

View File

@ -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

View File

@ -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;

View File

@ -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";

View File

@ -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";

View File

@ -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 =

View File

@ -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()

View File

@ -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);
} }