Make MediaSession2.Command updatable
Bug: 72619281
Test: build & runtest-MediaComponents
Change-Id: I917caaa09dfdc5dd981a555277a2a266dac8f5a0
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
index 30e32ec..4144342 100644
--- a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
@@ -53,10 +53,9 @@
private static final boolean DEBUG = true; // TODO(jaewan): Change
private final MediaController2 mInstance;
-
+ private final Context mContext;
private final Object mLock = new Object();
- private final Context mContext;
private final MediaSession2CallbackStub mSessionCallbackStub;
private final SessionToken2 mToken;
private final ControllerCallback mCallback;
@@ -209,6 +208,10 @@
return mCallbackExecutor;
}
+ Context getContext() {
+ return mContext;
+ }
+
@Override
public SessionToken2 getSessionToken_impl() {
return mToken;
@@ -606,7 +609,7 @@
return;
}
controller.onConnectionChangedNotLocked(
- sessionBinder, CommandGroup.fromBundle(commandGroup));
+ sessionBinder, CommandGroup.fromBundle(controller.getContext(), commandGroup));
}
@Override
@@ -645,7 +648,8 @@
}
List<CommandButton> layout = new ArrayList<>();
for (int i = 0; i < commandButtonlist.size(); i++) {
- CommandButton button = CommandButton.fromBundle(commandButtonlist.get(i));
+ CommandButton button = CommandButton.fromBundle(
+ browser.getContext(), commandButtonlist.get(i));
if (button != null) {
layout.add(button);
}
@@ -662,7 +666,7 @@
Log.w(TAG, "Don't fail silently here. Highly likely a bug");
return;
}
- Command command = Command.fromBundle(commandBundle);
+ Command command = Command.fromBundle(controller.getContext(), commandBundle);
if (command == null) {
return;
}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
index 7c36739..5bb608d 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -20,6 +20,8 @@
import static android.media.SessionToken2.TYPE_SESSION;
import static android.media.SessionToken2.TYPE_SESSION_SERVICE;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.Manifest.permission;
import android.app.PendingIntent;
import android.content.Context;
@@ -520,6 +522,96 @@
}
}
+ public static final class CommandImpl implements CommandProvider {
+ private static final String KEY_COMMAND_CODE
+ = "android.media.media_session2.command.command_code";
+ private static final String KEY_COMMAND_CUSTOM_COMMAND
+ = "android.media.media_session2.command.custom_command";
+ private static final String KEY_COMMAND_EXTRA
+ = "android.media.media_session2.command.extra";
+
+ private final Command mInstance;
+ private final int mCommandCode;
+ // Nonnull if it's custom command
+ private final String mCustomCommand;
+ private final Bundle mExtra;
+
+ public CommandImpl(Command instance, int commandCode) {
+ mInstance = instance;
+ mCommandCode = commandCode;
+ mCustomCommand = null;
+ mExtra = null;
+ }
+
+ public CommandImpl(Command instance, @NonNull String action, @Nullable Bundle extra) {
+ if (action == null) {
+ throw new IllegalArgumentException("action shouldn't be null");
+ }
+ mInstance = instance;
+ mCommandCode = MediaSession2.COMMAND_CODE_CUSTOM;
+ mCustomCommand = action;
+ mExtra = extra;
+ }
+
+ public int getCommandCode_impl() {
+ return mCommandCode;
+ }
+
+ public @Nullable String getCustomCommand_impl() {
+ return mCustomCommand;
+ }
+
+ public @Nullable Bundle getExtra_impl() {
+ return mExtra;
+ }
+
+ /**
+ * @ 7return a new Bundle instance from the Command
+ */
+ public Bundle toBundle_impl() {
+ Bundle bundle = new Bundle();
+ bundle.putInt(KEY_COMMAND_CODE, mCommandCode);
+ bundle.putString(KEY_COMMAND_CUSTOM_COMMAND, mCustomCommand);
+ bundle.putBundle(KEY_COMMAND_EXTRA, mExtra);
+ return bundle;
+ }
+
+ /**
+ * @return a new Command instance from the Bundle
+ */
+ public static Command fromBundle_impl(Context context, Bundle command) {
+ int code = command.getInt(KEY_COMMAND_CODE);
+ if (code != MediaSession2.COMMAND_CODE_CUSTOM) {
+ return new Command(context, code);
+ } else {
+ String customCommand = command.getString(KEY_COMMAND_CUSTOM_COMMAND);
+ if (customCommand == null) {
+ return null;
+ }
+ return new Command(context, customCommand, command.getBundle(KEY_COMMAND_EXTRA));
+ }
+ }
+
+ @Override
+ public boolean equals_impl(Object obj) {
+ if (!(obj instanceof CommandImpl)) {
+ return false;
+ }
+ CommandImpl other = (CommandImpl) obj;
+ // TODO(jaewan): Should we also compare contents in bundle?
+ // It may not be possible if the bundle contains private class.
+ return mCommandCode == other.mCommandCode
+ && TextUtils.equals(mCustomCommand, other.mCustomCommand);
+ }
+
+ @Override
+ public int hashCode_impl() {
+ final int prime = 31;
+ return ((mCustomCommand != null)
+ ? mCustomCommand.hashCode() : 0) * prime + mCommandCode;
+ }
+ }
+
public static class ControllerInfoImpl implements ControllerInfoProvider {
private final ControllerInfo mInstance;
private final int mUid;
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
index 4fc69b9..4bb5f47 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -16,6 +16,7 @@
package com.android.media;
+import android.content.Context;
import android.media.MediaItem2;
import android.media.MediaLibraryService2.BrowserRoot;
import android.media.MediaLibraryService2.MediaLibrarySessionCallback;
@@ -92,7 +93,8 @@
public void connect(String callingPackage, final IMediaSession2Callback callback)
throws RuntimeException {
final MediaSession2Impl sessionImpl = getSession();
- final ControllerInfo request = new ControllerInfo(sessionImpl.getContext(),
+ final Context context = sessionImpl.getContext();
+ final ControllerInfo request = new ControllerInfo(context,
Binder.getCallingUid(), Binder.getCallingPid(), callingPackage, callback);
sessionImpl.getCallbackExecutor().execute(() -> {
final MediaSession2Impl session = mSession.get();
@@ -111,7 +113,7 @@
}
if (allowedCommands == null) {
// For trusted apps, send non-null allowed commands to keep connection.
- allowedCommands = new CommandGroup();
+ allowedCommands = new CommandGroup(context);
}
}
if (DEBUG) {
@@ -178,7 +180,7 @@
return;
}
// TODO(jaewan): Sanity check.
- Command command = new Command(commandCode);
+ Command command = new Command(session.getContext(), commandCode);
boolean accepted = session.getCallback().onCommandRequest(controller, command);
if (!accepted) {
// Don't run rejected command.
@@ -248,7 +250,7 @@
if (session == null) {
return;
}
- final Command command = Command.fromBundle(commandBundle);
+ final Command command = Command.fromBundle(session.getContext(), commandBundle);
session.getCallback().onCustomCommand(controller, command, args, receiver);
});
}
diff --git a/packages/MediaComponents/src/com/android/media/SessionToken2Impl.java b/packages/MediaComponents/src/com/android/media/SessionToken2Impl.java
index c91a89c..b2b7959 100644
--- a/packages/MediaComponents/src/com/android/media/SessionToken2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/SessionToken2Impl.java
@@ -152,7 +152,7 @@
return mSessionBinder;
}
- public static SessionToken2 fromBundle(Context context, Bundle bundle) {
+ public static SessionToken2 fromBundle_impl(Context context, Bundle bundle) {
if (bundle == null) {
return null;
}
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
index b9d7612..0fc1ac1 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
@@ -32,6 +32,7 @@
import android.media.MediaMetadata2;
import android.media.MediaPlayerInterface;
import android.media.MediaSession2;
+import android.media.MediaSession2.Command;
import android.media.MediaSession2.ControllerInfo;
import android.media.MediaSession2.SessionCallback;
import android.media.MediaSessionService2;
@@ -110,6 +111,20 @@
}
@Override
+ public MediaSession2Provider.CommandProvider createMediaSession2Command(Command instance,
+ int commandCode, String action, Bundle extra) {
+ if (action == null && extra == null) {
+ return new MediaSession2Impl.CommandImpl(instance, commandCode);
+ }
+ return new MediaSession2Impl.CommandImpl(instance, action, extra);
+ }
+
+ @Override
+ public Command fromBundle_MediaSession2Command(Context context, Bundle command) {
+ return MediaSession2Impl.CommandImpl.fromBundle_impl(context, command);
+ }
+
+ @Override
public MediaSessionService2Provider createMediaSessionService2(
MediaSessionService2 instance) {
return new MediaSessionService2Impl(instance);
@@ -151,7 +166,7 @@
@Override
public SessionToken2 SessionToken2_fromBundle(Context context, Bundle bundle) {
- return SessionToken2Impl.fromBundle(context, bundle);
+ return SessionToken2Impl.fromBundle_impl(context, bundle);
}
@Override
diff --git a/packages/MediaComponents/test/src/android/media/MediaController2Test.java b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
index 9c5aa21..27dbaf8 100644
--- a/packages/MediaComponents/test/src/android/media/MediaController2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
@@ -68,7 +68,7 @@
// Create this test specific MediaSession2 to use our own Handler.
mPlayer = new MockPlayer(1);
mSession = new MediaSession2.Builder(mContext, mPlayer)
- .setSessionCallback(sHandlerExecutor, new SessionCallback())
+ .setSessionCallback(sHandlerExecutor, new SessionCallback(mContext))
.setId(TAG).build();
mController = createController(mSession.getToken());
TestServiceRegistry.getInstance().setHandler(sHandler);
@@ -256,12 +256,13 @@
@Test
public void testSendCustomCommand() throws InterruptedException {
// TODO(jaewan): Need to revisit with the permission.
- final Command testCommand = new Command(MediaSession2.COMMAND_CODE_PLAYBACK_PREPARE);
+ final Command testCommand =
+ new Command(mContext, MediaSession2.COMMAND_CODE_PLAYBACK_PREPARE);
final Bundle testArgs = new Bundle();
testArgs.putString("args", "testSendCustomAction");
final CountDownLatch latch = new CountDownLatch(1);
- final SessionCallback callback = new SessionCallback() {
+ final SessionCallback callback = new SessionCallback(mContext) {
@Override
public void onCustomCommand(ControllerInfo controller, Command customCommand,
Bundle args, ResultReceiver cb) {
@@ -291,7 +292,7 @@
@Test
public void testControllerCallback_sessionRejects() throws InterruptedException {
- final MediaSession2.SessionCallback sessionCallback = new SessionCallback() {
+ final MediaSession2.SessionCallback sessionCallback = new SessionCallback(mContext) {
@Override
public MediaSession2.CommandGroup onConnect(ControllerInfo controller) {
return null;
@@ -357,7 +358,7 @@
final MockPlayer player = new MockPlayer(0);
sessionHandler.postAndSync(() -> {
mSession = new MediaSession2.Builder(mContext, mPlayer)
- .setSessionCallback(sHandlerExecutor, new SessionCallback())
+ .setSessionCallback(sHandlerExecutor, new SessionCallback(mContext))
.setId("testDeadlock").build();
});
final MediaController2 controller = createController(mSession.getToken());
@@ -545,7 +546,7 @@
// Recreated session has different session stub, so previously created controller
// shouldn't be available.
mSession = new MediaSession2.Builder(mContext, mPlayer)
- .setSessionCallback(sHandlerExecutor, new SessionCallback())
+ .setSessionCallback(sHandlerExecutor, new SessionCallback(mContext))
.setId(id).build();
});
testNoInteraction();
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
index 6b10ccc..c5bcfff 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
@@ -66,7 +66,7 @@
super.setUp();
mPlayer = new MockPlayer(0);
mSession = new MediaSession2.Builder(mContext, mPlayer)
- .setSessionCallback(sHandlerExecutor, new SessionCallback()).build();
+ .setSessionCallback(sHandlerExecutor, new SessionCallback(mContext)).build();
}
@After
@@ -295,7 +295,8 @@
@Test
public void testSendCustomAction() throws InterruptedException {
- final Command testCommand = new Command(MediaSession2.COMMAND_CODE_PLAYBACK_PREPARE);
+ final Command testCommand =
+ new Command(mContext, MediaSession2.COMMAND_CODE_PLAYBACK_PREPARE);
final Bundle testArgs = new Bundle();
testArgs.putString("args", "testSendCustomAction");
@@ -335,6 +336,10 @@
}
public class MockOnConnectCallback extends SessionCallback {
+ public MockOnConnectCallback() {
+ super(mContext);
+ }
+
@Override
public MediaSession2.CommandGroup onConnect(ControllerInfo controllerInfo) {
if (Process.myUid() != controllerInfo.getUid()) {
@@ -351,6 +356,10 @@
public class MockOnCommandCallback extends SessionCallback {
public final ArrayList<MediaSession2.Command> commands = new ArrayList<>();
+ public MockOnCommandCallback() {
+ super(mContext);
+ }
+
@Override
public boolean onCommandRequest(ControllerInfo controllerInfo, MediaSession2.Command command) {
assertEquals(mContext.getPackageName(), controllerInfo.getPackageName());
diff --git a/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java b/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
index d0106fa..96ae8b7 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
@@ -60,7 +60,9 @@
// per test thread differs across the {@link MediaSession2} with the same TAG.
final MockPlayer player = new MockPlayer(1);
mSession = new MediaSession2.Builder(mContext, player)
- .setSessionCallback(sHandlerExecutor, new SessionCallback()).setId(TAG).build();
+ .setSessionCallback(sHandlerExecutor, new SessionCallback(mContext))
+ .setId(TAG)
+ .build();
ensureChangeInSession();
}
@@ -109,7 +111,7 @@
sHandler.postAndSync(() -> {
mSession.close();
mSession = new MediaSession2.Builder(mContext, new MockPlayer(0)).setId(TAG)
- .setSessionCallback(sHandlerExecutor, new SessionCallback() {
+ .setSessionCallback(sHandlerExecutor, new SessionCallback(mContext) {
@Override
public MediaSession2.CommandGroup onConnect(ControllerInfo controller) {
// Reject all connection request.
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
index c1187c2..6e1501a 100644
--- a/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
+++ b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
@@ -81,6 +81,10 @@
}
private class TestLibrarySessionCallback extends MediaLibrarySessionCallback {
+ public TestLibrarySessionCallback() {
+ super(MockMediaLibraryService2.this);
+ }
+
@Override
public CommandGroup onConnect(ControllerInfo controller) {
if (Process.myUid() != controller.getUid()) {
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
index 5c5c7d2..d85875e 100644
--- a/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
+++ b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
@@ -91,6 +91,10 @@
}
private class MySessionCallback extends SessionCallback {
+ public MySessionCallback() {
+ super(MockMediaSessionService2.this);
+ }
+
@Override
public MediaSession2.CommandGroup onConnect(ControllerInfo controller) {
if (Process.myUid() != controller.getUid()) {