MediaSession2/Controller2: Add playlist support
This CL implements following APIs:
- MediaSession2.get/setPlaylist
- MediaController2.getPlaylist
- MediaController2.ControllerCallback.onPlaylistChanged
Bug: 72537268
Test: Passed MediaSession2Test
Change-Id: I206bb1018cde38d7db296df0912d02272fe1c6c7
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
index 34aaa87..aabbc69 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
@@ -29,6 +29,7 @@
*/
oneway interface IMediaSession2Callback {
void onPlaybackStateChanged(in Bundle state);
+ void onPlaylistChanged(in List<Bundle> playlist);
void onPlaylistParamsChanged(in Bundle params);
/**
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
index 57624e2..30e32ec 100644
--- a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
@@ -70,6 +70,8 @@
@GuardedBy("mLock")
private PlaybackState2 mPlaybackState;
@GuardedBy("mLock")
+ private List<MediaItem2> mPlaylist;
+ @GuardedBy("mLock")
private PlaylistParams mPlaylistParams;
// Assignment should be used with the lock hold, but should be used without a lock to prevent
@@ -346,8 +348,9 @@
@Override
public List<MediaItem2> getPlaylist_impl() {
- // TODO(jaewan): Implement
- return null;
+ synchronized (mLock) {
+ return mPlaylist;
+ }
}
@Override
@@ -441,6 +444,26 @@
});
}
+ private void pushPlaylistChanges(final List<Bundle> list) {
+ final List<MediaItem2> playlist = new ArrayList<>();
+ for (int i = 0; i < list.size(); i++) {
+ MediaItem2 item = MediaItem2.fromBundle(mContext, list.get(i));
+ if (item != null) {
+ playlist.add(item);
+ }
+ }
+
+ synchronized (mLock) {
+ mPlaylist = playlist;
+ mCallbackExecutor.execute(() -> {
+ if (!mInstance.isConnected()) {
+ return;
+ }
+ mCallback.onPlaylistChanged(playlist);
+ });
+ }
+ }
+
// Called when the result for connecting to the session was delivered.
// Should be used without a lock to prevent potential deadlock.
private void onConnectionChangedNotLocked(IMediaSession2 sessionBinder,
@@ -546,6 +569,21 @@
}
@Override
+ public void onPlaylistChanged(List<Bundle> playlist) throws RuntimeException {
+ final MediaController2Impl controller;
+ try {
+ controller = getController();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ if (playlist == null) {
+ return;
+ }
+ controller.pushPlaylistChanges(playlist);
+ }
+
+ @Override
public void onPlaylistParamsChanged(Bundle params) throws RuntimeException {
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 a6ba767..7c36739 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -77,11 +77,14 @@
private MediaPlayerInterface mPlayer;
@GuardedBy("mLock")
private MyPlaybackListener mListener;
+ @GuardedBy("mLock")
private PlaylistParams mPlaylistParams;
+ @GuardedBy("mLock")
+ private List<MediaItem2> mPlaylist;
/**
* Can be only called by the {@link Builder#build()}.
- *
+ *
* @param instance
* @param context
* @param player
@@ -293,7 +296,11 @@
if (params == null) {
throw new IllegalArgumentException("PlaylistParams should not be null!");
}
- mPlaylistParams = params;
+ ensureCallingThread();
+ ensurePlayer();
+ synchronized (mLock) {
+ mPlaylistParams = params;
+ }
mPlayer.setPlaylistParams(params);
mSessionStub.notifyPlaylistParamsChanged(params);
}
@@ -334,14 +341,24 @@
}
@Override
- public void setPlaylist_impl(List<MediaItem2> playlist, PlaylistParams param) {
- // TODO(jaewan): Implement
+ public void setPlaylist_impl(List<MediaItem2> playlist) {
+ if (playlist == null) {
+ throw new IllegalArgumentException("Playlist should not be null!");
+ }
+ ensureCallingThread();
+ ensurePlayer();
+ synchronized (mLock) {
+ mPlaylist = playlist;
+ }
+ mPlayer.setPlaylist(playlist);
+ mSessionStub.notifyPlaylistChanged(playlist);
}
@Override
public List<MediaItem2> getPlaylist_impl() {
- // TODO(jaewan): Implement this
- return null;
+ synchronized (mLock) {
+ return mPlaylist;
+ }
}
@Override
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
index 4451b3c..4fc69b9 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.media.MediaItem2;
import android.media.MediaLibraryService2.BrowserRoot;
import android.media.MediaLibraryService2.MediaLibrarySessionCallback;
import android.media.MediaSession2;
@@ -345,6 +346,32 @@
}
}
+ public void notifyPlaylistChanged(List<MediaItem2> playlist) {
+ if (playlist == null) {
+ return;
+ }
+ final List<Bundle> bundleList = new ArrayList<>();
+ for (int i = 0; i < playlist.size(); i++) {
+ if (playlist.get(i) != null) {
+ Bundle bundle = playlist.get(i).toBundle();
+ if (bundle != null) {
+ bundleList.add(bundle);
+ }
+ }
+ }
+ final List<ControllerInfo> list = getControllers();
+ for (int i = 0; i < list.size(); i++) {
+ IMediaSession2Callback callbackBinder =
+ ControllerInfoImpl.from(list.get(i)).getControllerBinder();
+ try {
+ callbackBinder.onPlaylistChanged(bundleList);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Controller is gone", e);
+ // TODO(jaewan): What to do when the controller is gone?
+ }
+ }
+ }
+
public void notifyPlaylistParamsChanged(MediaSession2.PlaylistParams params) {
final List<ControllerInfo> list = getControllers();
for (int i = 0; i < list.size(); i++) {
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
index ed4fa89..6b10ccc 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
@@ -145,6 +145,30 @@
}
@Test
+ public void testSetPlaylist() throws Exception {
+ final List<MediaItem2> playlist = new ArrayList<>();
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final TestControllerCallbackInterface callback = new TestControllerCallbackInterface() {
+ @Override
+ public void onPlaylistChanged(List<MediaItem2> givenList) {
+ assertMediaItemListEquals(playlist, givenList);
+ latch.countDown();
+ }
+ };
+
+ final MediaController2 controller = createController(mSession.getToken(), true, callback);
+ mSession.setPlaylist(playlist);
+
+ assertTrue(mPlayer.mSetPlaylistCalled);
+ assertMediaItemListEquals(playlist, mPlayer.mPlaylist);
+ assertMediaItemListEquals(playlist, mSession.getPlaylist());
+
+ assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ assertMediaItemListEquals(playlist, controller.getPlaylist());
+ }
+
+ @Test
public void testSetPlaylistParams() throws Exception {
final PlaylistParams params = new PlaylistParams(
PlaylistParams.REPEAT_MODE_ALL,
@@ -339,4 +363,28 @@
return true;
}
}
+
+ private static void assertMediaItemListEquals(List<MediaItem2> a, List<MediaItem2> b) {
+ if (a == null || b == null) {
+ assertEquals(a, b);
+ }
+ assertEquals(a.size(), b.size());
+
+ for (int i = 0; i < a.size(); i++) {
+ MediaItem2 aItem = a.get(i);
+ MediaItem2 bItem = b.get(i);
+
+ if (aItem == null || bItem == null) {
+ assertEquals(aItem, bItem);
+ continue;
+ }
+
+ assertEquals(aItem.getMediaId(), bItem.getMediaId());
+ assertEquals(aItem.getFlags(), bItem.getFlags());
+ TestUtils.equals(aItem.getMetadata().getBundle(), bItem.getMetadata().getBundle());
+
+ // Note: Here it does not check whether DataSourceDesc are equal,
+ // since there DataSourceDec is not comparable.
+ }
+ }
}
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
index 982346b..8e1c782 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
@@ -61,6 +61,7 @@
interface TestControllerCallbackInterface {
// Add methods in ControllerCallback/BrowserCallback that you want to test.
+ default void onPlaylistChanged(List<MediaItem2> playlist) {}
default void onPlaylistParamsChanged(MediaSession2.PlaylistParams params) {}
// Currently empty. Add methods in ControllerCallback/BrowserCallback that you want to test.
@@ -217,6 +218,13 @@
}
@Override
+ public void onPlaylistChanged(List<MediaItem2> params) {
+ if (mCallbackProxy != null) {
+ mCallbackProxy.onPlaylistChanged(params);
+ }
+ }
+
+ @Override
public void onPlaylistParamsChanged(MediaSession2.PlaylistParams params) {
if (mCallbackProxy != null) {
mCallbackProxy.onPlaylistParamsChanged(params);
diff --git a/packages/MediaComponents/test/src/android/media/MockPlayer.java b/packages/MediaComponents/test/src/android/media/MockPlayer.java
index a0f56ab..1faf0f4 100644
--- a/packages/MediaComponents/test/src/android/media/MockPlayer.java
+++ b/packages/MediaComponents/test/src/android/media/MockPlayer.java
@@ -16,7 +16,6 @@
package android.media;
-import android.media.MediaPlayerInterface;
import android.media.MediaSession2.PlaylistParams;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -44,10 +43,13 @@
public long mSeekPosition;
public boolean mSetCurrentPlaylistItemCalled;
public int mItemIndex;
+ public boolean mSetPlaylistCalled;
public boolean mSetPlaylistParamsCalled;
public List<PlaybackListenerHolder> mListeners = new ArrayList<>();
+ public List<MediaItem2> mPlaylist;
public PlaylistParams mPlaylistParams;
+
private PlaybackState2 mLastPlaybackState;
private AudioAttributes mAudioAttributes;
@@ -194,11 +196,13 @@
}
@Override
- public void setPlaylist(List<MediaItem2> item, PlaylistParams param) {
+ public void setPlaylist(List<MediaItem2> playlist) {
+ mSetPlaylistCalled = true;
+ mPlaylist = playlist;
}
@Override
public List<MediaItem2> getPlaylist() {
- return null;
+ return mPlaylist;
}
}