Merge "MediaSession2: Implement setAllowedCommands()" into pi-dev
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
index 9ee4928..3d812f8 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
@@ -41,6 +41,7 @@
void onDisconnected();
void onCustomLayoutChanged(in List<Bundle> commandButtonlist);
+ void onAllowedCommandsChanged(in Bundle commands);
void onCustomCommand(in Bundle command, in Bundle args, in ResultReceiver receiver);
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
index c76cd6c..dd23148 100644
--- a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
@@ -776,6 +776,12 @@
});
}
+ void onAllowedCommandsChanged(final CommandGroup commands) {
+ mCallbackExecutor.execute(() -> {
+ mCallback.onAllowedCommandsChanged(mInstance, commands);
+ });
+ }
+
void onCustomLayoutChanged(final List<CommandButton> layout) {
mCallbackExecutor.execute(() -> {
mCallback.onCustomLayoutChanged(mInstance, layout);
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java b/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
index b8e651e..451368f 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
@@ -215,6 +215,27 @@
}
@Override
+ public void onAllowedCommandsChanged(Bundle commandsBundle) {
+ final MediaController2Impl controller;
+ try {
+ controller = getController();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ if (controller == null) {
+ // TODO(jaewan): Revisit here. Could be a bug
+ return;
+ }
+ CommandGroup commands = CommandGroup.fromBundle(controller.getContext(), commandsBundle);
+ if (commands == null) {
+ Log.w(TAG, "onAllowedCommandsChanged(): Ignoring null commands");
+ return;
+ }
+ controller.onAllowedCommandsChanged(commands);
+ }
+
+ @Override
public void onCustomCommand(Bundle commandBundle, Bundle args, ResultReceiver receiver) {
final MediaController2Impl controller;
try {
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
index 2a28291..1f24d4e 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -429,7 +429,13 @@
@Override
public void setAllowedCommands_impl(ControllerInfo controller, CommandGroup commands) {
- // TODO(jaewan): Implement
+ if (controller == null) {
+ throw new IllegalArgumentException("controller shouldn't be null");
+ }
+ if (commands == null) {
+ throw new IllegalArgumentException("commands shouldn't be null");
+ }
+ mSessionStub.setAllowedCommands(controller, commands);
}
@Override
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
index ae4b17f..d4164f6 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -958,6 +958,22 @@
}
}
+ public void setAllowedCommands(ControllerInfo controller, CommandGroup commands) {
+ synchronized (mLock) {
+ mAllowedCommandGroupMap.put(controller, commands);
+ }
+ final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+ if (controllerBinder == null) {
+ return;
+ }
+ try {
+ controllerBinder.onAllowedCommandsChanged(commands.toBundle());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Controller is gone", e);
+ // TODO(jaewan): What to do when the controller is gone?
+ }
+ }
+
public void sendCustomCommand(ControllerInfo controller, Command command, Bundle args,
ResultReceiver receiver) {
if (receiver != null && controller == null) {
diff --git a/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
index 07f7711..e58bd02 100644
--- a/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
@@ -499,13 +499,17 @@
mCallbackProxy.onCustomCommand(command, args, receiver);
}
-
@Override
public void onCustomLayoutChanged(MediaController2 controller, List<CommandButton> layout) {
mCallbackProxy.onCustomLayoutChanged(layout);
}
@Override
+ public void onAllowedCommandsChanged(MediaController2 controller, CommandGroup commands) {
+ mCallbackProxy.onAllowedCommandsChanged(commands);
+ }
+
+ @Override
public void onGetLibraryRootDone(MediaBrowser2 browser, Bundle rootHints,
String rootMediaId, Bundle rootExtra) {
super.onGetLibraryRootDone(browser, rootHints, rootMediaId, rootExtra);
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
index 2c7de5c..afb191f 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
@@ -42,7 +42,6 @@
import android.support.annotation.NonNull;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import android.text.TextUtils;
import org.junit.After;
import org.junit.Before;
@@ -426,6 +425,38 @@
}
@Test
+ public void testSetAllowedCommands() throws InterruptedException {
+ final CommandGroup commands = new CommandGroup(mContext);
+ commands.addCommand(new Command(mContext, MediaSession2.COMMAND_CODE_PLAYBACK_PLAY));
+ commands.addCommand(new Command(mContext, MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE));
+ commands.addCommand(new Command(mContext, MediaSession2.COMMAND_CODE_PLAYBACK_STOP));
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final TestControllerCallbackInterface callback = new TestControllerCallbackInterface() {
+ @Override
+ public void onAllowedCommandsChanged(CommandGroup commandsOut) {
+ assertNotNull(commandsOut);
+ List<Command> expected = commands.getCommands();
+ List<Command> actual = commandsOut.getCommands();
+
+ assertNotNull(actual);
+ assertEquals(expected.size(), actual.size());
+ for (int i = 0; i < expected.size(); i++) {
+ assertEquals(expected.get(i), actual.get(i));
+ }
+ latch.countDown();
+ }
+ };
+
+ final MediaController2 controller = createController(mSession.getToken(), true, callback);
+ ControllerInfo controllerInfo = getTestControllerInfo();
+ assertNotNull(controllerInfo);
+
+ mSession.setAllowedCommands(controllerInfo, commands);
+ assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ }
+
+ @Test
public void testSendCustomAction() throws InterruptedException {
final Command testCommand =
new Command(mContext, MediaSession2.COMMAND_CODE_PLAYBACK_PREPARE);
@@ -457,13 +488,12 @@
private ControllerInfo getTestControllerInfo() {
List<ControllerInfo> controllers = mSession.getConnectedControllers();
assertNotNull(controllers);
- final String packageName = mContext.getPackageName();
for (int i = 0; i < controllers.size(); i++) {
- if (TextUtils.equals(packageName, controllers.get(i).getPackageName())) {
+ if (Process.myUid() == controllers.get(i).getUid()) {
return controllers.get(i);
}
}
- fail("Fails to get custom command");
+ fail("Failed to get test controller info");
return null;
}
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
index b32400f..83706c0 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
@@ -69,6 +69,7 @@
default void onPlaybackInfoChanged(MediaController2.PlaybackInfo info) {}
default void onPlaybackStateChanged(PlaybackState2 state) {}
default void onCustomLayoutChanged(List<CommandButton> layout) {}
+ default void onAllowedCommandsChanged(CommandGroup commands) {}
default void onCustomCommand(Command command, Bundle args, ResultReceiver receiver) {}
}
@@ -247,6 +248,11 @@
public void onCustomLayoutChanged(MediaController2 controller, List<CommandButton> layout) {
mCallbackProxy.onCustomLayoutChanged(layout);
}
+
+ @Override
+ public void onAllowedCommandsChanged(MediaController2 controller, CommandGroup commands) {
+ mCallbackProxy.onAllowedCommandsChanged(commands);
+ }
}
public class TestMediaController extends MediaController2 implements TestControllerInterface {
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2_PermissionTest.java b/packages/MediaComponents/test/src/android/media/MediaSession2_PermissionTest.java
index ae818e2..ca36513 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSession2_PermissionTest.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2_PermissionTest.java
@@ -16,13 +16,33 @@
package android.media;
-import static android.media.MediaSession2.*;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_FAST_FORWARD;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_PLAY;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_REWIND;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_SEEK_TO;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_STOP;
+import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_MEDIA_ID;
+import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_SEARCH;
+import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_URI;
+import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_MEDIA_ID;
+import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_SEARCH;
+import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_URI;
+import static android.media.MediaSession2.ControllerInfo;
+import static android.media.MediaSession2.PlaylistParams;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
@@ -41,6 +61,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.List;
+
/**
* Tests whether {@link MediaSession2} receives commands that hasn't allowed.
*/
@@ -262,7 +284,8 @@
verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(
matchesSession(), matchesCaller(), matches(COMMAND_CODE_PLAYBACK_SET_VOLUME));
- createSessionWithAllowedActions(createCommandGroupWithout(COMMAND_CODE_PLAYBACK_SET_VOLUME));
+ createSessionWithAllowedActions(
+ createCommandGroupWithout(COMMAND_CODE_PLAYBACK_SET_VOLUME));
createController(mSession.getToken()).setVolumeTo(0, 0);
verify(mCallback, after(WAIT_TIME_MS).never()).onCommandRequest(any(), any(), any());
}
@@ -359,4 +382,43 @@
verify(mCallback, after(WAIT_TIME_MS).never()).onPrepareFromSearch(
any(), any(), any(), any());
}
+
+ @Test
+ public void testChangingPermissionWithSetAllowedCommands() throws InterruptedException {
+ final String query = "testChangingPermissionWithSetAllowedCommands";
+ createSessionWithAllowedActions(
+ createCommandGroupWith(COMMAND_CODE_PREPARE_FROM_SEARCH));
+
+ TestControllerCallbackInterface controllerCallback =
+ mock(TestControllerCallbackInterface.class);
+ MediaController2 controller =
+ createController(mSession.getToken(), true, controllerCallback);
+
+ controller.prepareFromSearch(query, null);
+ verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onPrepareFromSearch(
+ matchesSession(), matchesCaller(), eq(query), isNull());
+ clearInvocations(mCallback);
+
+ // Change allowed commands.
+ mSession.setAllowedCommands(getTestControllerInfo(),
+ createCommandGroupWithout(COMMAND_CODE_PREPARE_FROM_SEARCH));
+ verify(controllerCallback, timeout(TIMEOUT_MS).atLeastOnce())
+ .onAllowedCommandsChanged(any());
+
+ controller.prepareFromSearch(query, null);
+ verify(mCallback, after(WAIT_TIME_MS).never()).onPrepareFromSearch(
+ any(), any(), any(), any());
+ }
+
+ private ControllerInfo getTestControllerInfo() {
+ List<ControllerInfo> controllers = mSession.getConnectedControllers();
+ assertNotNull(controllers);
+ for (int i = 0; i < controllers.size(); i++) {
+ if (Process.myUid() == controllers.get(i).getUid()) {
+ return controllers.get(i);
+ }
+ }
+ fail("Failed to get test controller info");
+ return null;
+ }
}