diff --git a/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/MediaSessionConstants.java b/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/MediaSessionConstants.java
index 2e4040f2d9..4791aad8fc 100644
--- a/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/MediaSessionConstants.java
+++ b/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/MediaSessionConstants.java
@@ -23,6 +23,7 @@ public class MediaSessionConstants {
// Test method names
public static final String TEST_GET_SESSION_ACTIVITY = "testGetSessionActivity";
+ 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_IS_SESSION_COMMAND_AVAILABLE = "testIsSessionCommandAvailable";
diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerCompatCallbackWithMediaSessionCompatTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerCompatCallbackWithMediaSessionCompatTest.java
new file mode 100644
index 0000000000..29e36e682d
--- /dev/null
+++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerCompatCallbackWithMediaSessionCompatTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.media3.session;
+
+import static androidx.media3.test.session.common.CommonConstants.DEFAULT_TEST_NAME;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+import androidx.media3.test.session.common.HandlerThreadTestRule;
+import androidx.media3.test.session.common.MainLooperTestRule;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link MediaControllerCompat.Callback} with {@link MediaSessionCompat}.
+ *
+ *
The tests in this class represents the reference specific usages of the legacy API for which
+ * we need to provide support with the Media3 API.
+ *
+ *
So these test are actually not tests but rather a reference of how the legacy API is used and
+ * expected to work.
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class MediaControllerCompatCallbackWithMediaSessionCompatTest {
+
+ private static final int TIMEOUT_MS = 1_000;
+
+ @ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
+
+ private final HandlerThreadTestRule threadTestRule =
+ new HandlerThreadTestRule("MediaControllerCompatCallbackWithMediaSessionCompatTest");
+ private final MediaControllerTestRule controllerTestRule =
+ new MediaControllerTestRule(threadTestRule);
+
+ @Rule
+ public final TestRule chain = RuleChain.outerRule(threadTestRule).around(controllerTestRule);
+
+ private Context context;
+ private RemoteMediaSessionCompat session;
+
+ @Before
+ public void setUp() throws Exception {
+ context = ApplicationProvider.getApplicationContext();
+ session = new RemoteMediaSessionCompat(DEFAULT_TEST_NAME, context);
+ }
+
+ @After
+ public void cleanUp() throws RemoteException {
+ session.cleanUp();
+ }
+
+ /** Custom actions in the legacy session used for instance by Android Auto and Wear OS. */
+ @Test
+ public void setPlaybackState_withCustomActions_onPlaybackStateCompatChangedCalled()
+ throws Exception {
+ MediaSessionCompat.Token sessionToken = session.getSessionToken();
+ Bundle extras1 = new Bundle();
+ extras1.putString("key", "value-1");
+ PlaybackStateCompat.CustomAction customAction1 =
+ new PlaybackStateCompat.CustomAction.Builder("action1", "actionName1", /* icon= */ 1)
+ .setExtras(extras1)
+ .build();
+ Bundle extras2 = new Bundle();
+ extras2.putString("key", "value-2");
+ PlaybackStateCompat.CustomAction customAction2 =
+ new PlaybackStateCompat.CustomAction.Builder("action2", "actionName2", /* icon= */ 2)
+ .setExtras(extras2)
+ .build();
+ PlaybackStateCompat.Builder builder =
+ new PlaybackStateCompat.Builder()
+ .addCustomAction(customAction1)
+ .addCustomAction(customAction2);
+ List receivedActions = new ArrayList<>();
+ List receivedDisplayNames = new ArrayList<>();
+ List receivedBundleValues = new ArrayList<>();
+ List receivedIconResIds = new ArrayList<>();
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ threadTestRule
+ .getHandler()
+ .postAndSync(
+ () -> {
+ MediaControllerCompat mediaControllerCompat =
+ new MediaControllerCompat(context, sessionToken);
+ mediaControllerCompat.registerCallback(
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onPlaybackStateChanged(PlaybackStateCompat state) {
+ List layout = state.getCustomActions();
+ for (PlaybackStateCompat.CustomAction action : layout) {
+ receivedActions.add(action.getAction());
+ receivedDisplayNames.add(String.valueOf(action.getName()));
+ receivedBundleValues.add(action.getExtras().getString("key"));
+ receivedIconResIds.add(action.getIcon());
+ }
+ countDownLatch.countDown();
+ }
+ });
+ });
+
+ session.setPlaybackState(builder.build());
+
+ assertThat(countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(receivedActions).containsExactly("action1", "action2").inOrder();
+ assertThat(receivedDisplayNames).containsExactly("actionName1", "actionName2").inOrder();
+ assertThat(receivedIconResIds).containsExactly(1, 2).inOrder();
+ assertThat(receivedBundleValues).containsExactly("value-1", "value-2").inOrder();
+ }
+}
diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerCompatCallbackWithMediaSessionTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerCompatCallbackWithMediaSessionTest.java
index 2a7af750fd..d025108b07 100644
--- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerCompatCallbackWithMediaSessionTest.java
+++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerCompatCallbackWithMediaSessionTest.java
@@ -769,52 +769,54 @@ public class MediaControllerCompatCallbackWithMediaSessionTest {
}
@Test
- public void customLayoutChanged_updatesPlaybackStateCompat() throws Exception {
-
- AtomicReference playbackStateRef = new AtomicReference<>();
+ public void setCustomLayout_onPlaybackStateCompatChangedCalled() throws Exception {
+ List buttons = new ArrayList<>();
+ Bundle extras1 = new Bundle();
+ extras1.putString("key", "value-1");
+ CommandButton button1 =
+ new CommandButton.Builder()
+ .setSessionCommand(new SessionCommand("action1", extras1))
+ .setDisplayName("actionName1")
+ .setIconResId(1)
+ .build();
+ Bundle extras2 = new Bundle();
+ extras2.putString("key", "value-2");
+ CommandButton button2 =
+ new CommandButton.Builder()
+ .setSessionCommand(new SessionCommand("action2", extras2))
+ .setDisplayName("actionName2")
+ .setIconResId(2)
+ .build();
+ buttons.add(button1);
+ buttons.add(button2);
+ List receivedActions = new ArrayList<>();
+ List receivedDisplayNames = new ArrayList<>();
+ List receivedBundleValues = new ArrayList<>();
+ List receivedIconResIds = new ArrayList<>();
CountDownLatch latch = new CountDownLatch(1);
MediaControllerCompat.Callback callback =
new MediaControllerCompat.Callback() {
@Override
public void onPlaybackStateChanged(PlaybackStateCompat state) {
- playbackStateRef.set(state);
+ List layout = state.getCustomActions();
+ for (PlaybackStateCompat.CustomAction action : layout) {
+ receivedActions.add(action.getAction());
+ receivedDisplayNames.add(String.valueOf(action.getName()));
+ receivedBundleValues.add(action.getExtras().getString("key"));
+ receivedIconResIds.add(action.getIcon());
+ }
latch.countDown();
}
};
controllerCompat.registerCallback(callback, handler);
- List customLayout = new ArrayList<>();
- Bundle customCommandBundle1 = new Bundle();
- customCommandBundle1.putString("customKey1", "customValue1");
- customLayout.add(
- new CommandButton.Builder()
- .setDisplayName("customCommandName1")
- .setIconResId(1)
- .setSessionCommand(new SessionCommand("customCommandAction1", customCommandBundle1))
- .build());
- Bundle customCommandBundle2 = new Bundle();
- customCommandBundle2.putString("customKey2", "customValue2");
- customLayout.add(
- new CommandButton.Builder()
- .setDisplayName("customCommandName2")
- .setIconResId(2)
- .setSessionCommand(new SessionCommand("customCommandAction2", customCommandBundle2))
- .build());
-
- session.setCustomLayout(customLayout);
+ session.setCustomLayout(buttons);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
- List customActions =
- playbackStateRef.get().getCustomActions();
- assertThat(customActions).hasSize(2);
- assertThat(customActions.get(0).getAction()).isEqualTo("customCommandAction1");
- assertThat(customActions.get(0).getName()).isEqualTo("customCommandName1");
- assertThat(customActions.get(0).getIcon()).isEqualTo(1);
- assertThat(TestUtils.equals(customActions.get(0).getExtras(), customCommandBundle1)).isTrue();
- assertThat(customActions.get(1).getAction()).isEqualTo("customCommandAction2");
- assertThat(customActions.get(1).getName()).isEqualTo("customCommandName2");
- assertThat(customActions.get(1).getIcon()).isEqualTo(2);
- assertThat(TestUtils.equals(customActions.get(1).getExtras(), customCommandBundle2)).isTrue();
+ assertThat(receivedActions).containsExactly("action1", "action2").inOrder();
+ assertThat(receivedDisplayNames).containsExactly("actionName1", "actionName2").inOrder();
+ assertThat(receivedIconResIds).containsExactly(1, 2).inOrder();
+ assertThat(receivedBundleValues).containsExactly("value-1", "value-2").inOrder();
}
@Test
diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerTest.java
index ebc4315467..378506b08e 100644
--- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerTest.java
+++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerTest.java
@@ -15,7 +15,6 @@
*/
package androidx.media3.session;
-import static androidx.media3.common.Player.COMMAND_PLAY_PAUSE;
import static androidx.media3.common.Player.COMMAND_SET_REPEAT_MODE;
import static androidx.media3.common.Player.EVENT_REPEAT_MODE_CHANGED;
import static androidx.media3.common.Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED;
@@ -30,6 +29,7 @@ import static androidx.media3.test.session.common.CommonConstants.DEFAULT_TEST_N
import static androidx.media3.test.session.common.CommonConstants.MOCK_MEDIA3_LIBRARY_SERVICE;
import static androidx.media3.test.session.common.CommonConstants.MOCK_MEDIA3_SESSION_SERVICE;
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_CONTROLLER_LISTENER_SESSION_REJECTS;
+import static androidx.media3.test.session.common.MediaSessionConstants.TEST_WITH_CUSTOM_COMMANDS;
import static androidx.media3.test.session.common.TestUtils.LONG_TIMEOUT_MS;
import static androidx.media3.test.session.common.TestUtils.NO_RESPONSE_TIMEOUT_MS;
import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS;
@@ -1743,37 +1743,61 @@ public class MediaControllerListenerTest {
}
@Test
- public void onCustomLayoutChanged() throws Exception {
+ public void setCustomLayout_onSetCustomLayoutCalled() throws Exception {
List buttons = new ArrayList<>();
-
- CommandButton button =
+ Bundle extras1 = new Bundle();
+ extras1.putString("key", "value-1");
+ CommandButton button1 =
new CommandButton.Builder()
- .setPlayerCommand(COMMAND_PLAY_PAUSE)
- .setDisplayName("button")
+ .setSessionCommand(new SessionCommand("action1", extras1))
+ .setDisplayName("actionName1")
+ .setIconResId(1)
.build();
- buttons.add(button);
-
+ Bundle extras2 = new Bundle();
+ extras2.putString("key", "value-2");
+ CommandButton button2 =
+ new CommandButton.Builder()
+ .setSessionCommand(new SessionCommand("action2", extras2))
+ .setDisplayName("actionName2")
+ .setIconResId(2)
+ .build();
+ buttons.add(button1);
+ buttons.add(button2);
CountDownLatch latch = new CountDownLatch(1);
+ List receivedActions = new ArrayList<>();
+ List receivedDisplayNames = new ArrayList<>();
+ List receivedBundleValues = new ArrayList<>();
+ List receivedIconResIds = new ArrayList<>();
+ List receivedCommandCodes = new ArrayList<>();
MediaController.Listener listener =
new MediaController.Listener() {
@Override
public ListenableFuture onSetCustomLayout(
MediaController controller, List layout) {
- assertThat(layout).hasSize(buttons.size());
- for (int i = 0; i < layout.size(); i++) {
- assertThat(layout.get(i).playerCommand).isEqualTo(buttons.get(i).playerCommand);
- assertThat(layout.get(i).displayName.toString())
- .isEqualTo(buttons.get(i).displayName.toString());
+ for (CommandButton button : layout) {
+ receivedActions.add(button.sessionCommand.customAction);
+ receivedDisplayNames.add(String.valueOf(button.displayName));
+ receivedBundleValues.add(button.sessionCommand.customExtras.getString("key"));
+ receivedCommandCodes.add(button.sessionCommand.commandCode);
+ receivedIconResIds.add(button.iconResId);
}
latch.countDown();
return Futures.immediateFuture(new SessionResult(RESULT_SUCCESS));
}
};
- controllerTestRule.createController(
- remoteSession.getToken(), /* connectionHints= */ null, listener);
+ RemoteMediaSession session = createRemoteMediaSession(TEST_WITH_CUSTOM_COMMANDS);
+ controllerTestRule.createController(session.getToken(), /* connectionHints= */ null, listener);
+
+ session.setCustomLayout(buttons);
- remoteSession.setCustomLayout(buttons);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(receivedActions).containsExactly("action1", "action2").inOrder();
+ assertThat(receivedCommandCodes)
+ .containsExactly(SessionCommand.COMMAND_CODE_CUSTOM, SessionCommand.COMMAND_CODE_CUSTOM)
+ .inOrder();
+ assertThat(receivedDisplayNames).containsExactly("actionName1", "actionName2").inOrder();
+ assertThat(receivedIconResIds).containsExactly(1, 2).inOrder();
+ assertThat(receivedBundleValues).containsExactly("value-1", "value-2").inOrder();
}
@Test
diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerWithMediaSessionCompatTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerWithMediaSessionCompatTest.java
index c22064fdc0..67a02b3b44 100644
--- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerWithMediaSessionCompatTest.java
+++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerWithMediaSessionCompatTest.java
@@ -16,12 +16,14 @@
package androidx.media3.session;
import static androidx.media3.common.Player.EVENT_REPEAT_MODE_CHANGED;
+import static androidx.media3.session.SessionResult.RESULT_SUCCESS;
import static androidx.media3.test.session.common.CommonConstants.DEFAULT_TEST_NAME;
import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
+import android.os.Bundle;
import android.os.RemoteException;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
@@ -33,6 +35,10 @@ import androidx.media3.test.session.common.MainLooperTestRule;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
@@ -108,4 +114,58 @@ public class MediaControllerListenerWithMediaSessionCompatTest {
assertThat(listenerEventCodes).containsExactly(EVENT_REPEAT_MODE_CHANGED, EVENT_ON_EVENTS);
assertThat(eventsRef.get()).isEqualTo(testEvents);
}
+
+ @Test
+ public void setPlaybackState_withCustomActions_onSetCustomLayoutCalled() throws Exception {
+ Bundle extras1 = new Bundle();
+ extras1.putString("key", "value-1");
+ PlaybackStateCompat.CustomAction customAction1 =
+ new PlaybackStateCompat.CustomAction.Builder("action1", "actionName1", /* icon= */ 1)
+ .setExtras(extras1)
+ .build();
+ Bundle extras2 = new Bundle();
+ extras2.putString("key", "value-2");
+ PlaybackStateCompat.CustomAction customAction2 =
+ new PlaybackStateCompat.CustomAction.Builder("action2", "actionName2", /* icon= */ 2)
+ .setExtras(extras2)
+ .build();
+ PlaybackStateCompat.Builder builder =
+ new PlaybackStateCompat.Builder()
+ .addCustomAction(customAction1)
+ .addCustomAction(customAction2);
+ List receivedActions = new ArrayList<>();
+ List receivedDisplayNames = new ArrayList<>();
+ List receivedBundleValues = new ArrayList<>();
+ List receivedIconResIds = new ArrayList<>();
+ List receivedCommandCodes = new ArrayList<>();
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ controllerTestRule.createController(
+ session.getSessionToken(),
+ new MediaController.Listener() {
+ @Override
+ public ListenableFuture onSetCustomLayout(
+ MediaController controller, List layout) {
+ for (CommandButton button : layout) {
+ receivedActions.add(button.sessionCommand.customAction);
+ receivedDisplayNames.add(String.valueOf(button.displayName));
+ receivedBundleValues.add(button.sessionCommand.customExtras.getString("key"));
+ receivedCommandCodes.add(button.sessionCommand.commandCode);
+ receivedIconResIds.add(button.iconResId);
+ }
+ countDownLatch.countDown();
+ return Futures.immediateFuture(new SessionResult(RESULT_SUCCESS));
+ }
+ });
+
+ session.setPlaybackState(builder.build());
+
+ assertThat(countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(receivedActions).containsExactly("action1", "action2").inOrder();
+ assertThat(receivedCommandCodes)
+ .containsExactly(SessionCommand.COMMAND_CODE_CUSTOM, SessionCommand.COMMAND_CODE_CUSTOM)
+ .inOrder();
+ assertThat(receivedDisplayNames).containsExactly("actionName1", "actionName2").inOrder();
+ assertThat(receivedIconResIds).containsExactly(1, 2).inOrder();
+ assertThat(receivedBundleValues).containsExactly("value-1", "value-2").inOrder();
+ }
}
diff --git a/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java b/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java
index 25202ac0ac..3b3b06e3bb 100644
--- a/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java
+++ b/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java
@@ -57,6 +57,7 @@ import static androidx.media3.test.session.common.MediaSessionConstants.KEY_AVAI
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_CONTROLLER_LISTENER_SESSION_REJECTS;
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_WITH_CUSTOM_COMMANDS;
import android.app.PendingIntent;
import android.app.Service;
@@ -177,6 +178,24 @@ public class MediaSessionProviderService extends Service {
builder.setSessionActivity(pendingIntent);
break;
}
+ case TEST_WITH_CUSTOM_COMMANDS:
+ {
+ SessionCommands availableSessionCommands =
+ new SessionCommands.Builder()
+ .add(new SessionCommand("action1", Bundle.EMPTY))
+ .add(new SessionCommand("action2", Bundle.EMPTY))
+ .build();
+ builder.setCallback(
+ new MediaSession.Callback() {
+ @Override
+ public MediaSession.ConnectionResult onConnect(
+ MediaSession session, ControllerInfo controller) {
+ return MediaSession.ConnectionResult.accept(
+ availableSessionCommands, Player.Commands.EMPTY);
+ }
+ });
+ break;
+ }
case TEST_CONTROLLER_LISTENER_SESSION_REJECTS:
{
builder.setCallback(