MediaSession2: Initial commit of MediaLibraryService2
MediaLibraryService2 is the new name for the MediaBrowserService
Test: Run all MediaComponents tests once
Change-Id: I0a29c4015cd22b5fa4e4e0f55562afd865eea1d6
diff --git a/packages/MediaComponents/test/AndroidManifest.xml b/packages/MediaComponents/test/AndroidManifest.xml
index fe16583..30bac87 100644
--- a/packages/MediaComponents/test/AndroidManifest.xml
+++ b/packages/MediaComponents/test/AndroidManifest.xml
@@ -32,10 +32,17 @@
<!-- Keep the test services synced together with the TestUtils.java -->
<service android:name="android.media.MockMediaSessionService2">
<intent-filter>
- <action android:name="android.media.session.MediaSessionService2" />
+ <action android:name="android.media.MediaSessionService2" />
</intent-filter>
<meta-data android:name="android.media.session" android:value="TestSession" />
</service>
+ <!-- Keep the test services synced together with the MockMediaLibraryService -->
+ <service android:name="android.media.MockMediaLibraryService2">
+ <intent-filter>
+ <action android:name="android.media.MediaLibraryService2" />
+ </intent-filter>
+ <meta-data android:name="android.media.session" android:value="TestBrowser" />
+ </service>
</application>
<instrumentation
diff --git a/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
index 2d8ca8e..fe8aeb9 100644
--- a/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
@@ -16,6 +16,7 @@
package android.media;
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -28,6 +29,7 @@
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.concurrent.CountDownLatch;
@@ -43,7 +45,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class MediaBrowser2Test extends MediaController2Test {
- private static final String TAG = "MediaPlayerBrowserTest";
+ private static final String TAG = "MediaBrowser2Test";
@Override
TestControllerInterface onCreateController(@NonNull SessionToken token,
@@ -51,6 +53,29 @@
return new TestMediaBrowser(mContext, token, new TestBrowserCallback(callback));
}
+ @Test
+ public void testGetBrowserRoot() throws InterruptedException {
+ final Bundle param = new Bundle();
+ param.putString(TAG, TAG);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final TestControllerCallbackInterface callback = new TestControllerCallbackInterface() {
+ @Override
+ public void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {
+ assertTrue(TestUtils.equals(param, rootHints));
+ assertEquals(MockMediaLibraryService2.ROOT_ID, rootMediaId);
+ assertTrue(TestUtils.equals(MockMediaLibraryService2.EXTRA, rootExtra));
+ latch.countDown();
+ }
+ };
+
+ final SessionToken token = MockMediaLibraryService2.getToken(mContext);
+ MediaBrowser2 browser =
+ (MediaBrowser2) createController(token,true, callback);
+ browser.getBrowserRoot(param);
+ assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ }
+
public static class TestBrowserCallback extends BrowserCallback
implements WaitForConnectionInterface {
private final TestControllerCallbackInterface mCallbackProxy;
@@ -77,7 +102,7 @@
@Override
public void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {
- // No-op here.
+ mCallbackProxy.onGetRootResult(rootHints, rootMediaId, rootExtra);
}
@Override
diff --git a/packages/MediaComponents/test/src/android/media/MediaController2Test.java b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
index 2aeb4ef..88ca106 100644
--- a/packages/MediaComponents/test/src/android/media/MediaController2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
@@ -51,9 +51,9 @@
public class MediaController2Test extends MediaSession2TestBase {
private static final String TAG = "MediaController2Test";
- private MediaSession2 mSession;
- private MediaController2 mController;
- private MockPlayer mPlayer;
+ MediaSession2 mSession;
+ MediaController2 mController;
+ MockPlayer mPlayer;
@Before
@Override
@@ -331,11 +331,23 @@
mPlayer = (MockPlayer) mSession.getPlayer();
}
+ // TODO(jaewan): Reenable when session manager detects app installs
@Ignore
@Test
- public void testConnectToService() throws InterruptedException {
+ public void testConnectToService_sessionService() throws InterruptedException {
connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+ testConnectToService();
+ }
+ // TODO(jaewan): Reenable when session manager detects app installs
+ @Ignore
+ @Test
+ public void testConnectToService_libraryService() throws InterruptedException {
+ connectToService(TestUtils.getServiceToken(mContext, MockMediaLibraryService2.ID));
+ testConnectToService();
+ }
+
+ public void testConnectToService() throws InterruptedException {
TestServiceRegistry serviceInfo = TestServiceRegistry.getInstance();
ControllerInfo info = serviceInfo.getOnConnectControllerInfo();
assertEquals(mContext.getPackageName(), info.getPackageName());
@@ -396,10 +408,24 @@
testControllerAfterSessionIsGone(id);
}
+ // TODO(jaewan): Reenable when session manager detects app installs
@Ignore
@Test
public void testRelease_sessionService() throws InterruptedException {
connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+ testReleaseFromService();
+ }
+
+ // TODO(jaewan): Reenable when session manager detects app installs
+ @Ignore
+ @Test
+ public void testRelease_libraryService() throws InterruptedException {
+ connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+ testReleaseFromService();
+ }
+
+ private void testReleaseFromService() throws InterruptedException {
+ final String id = mController.getSessionToken().getId();
final CountDownLatch latch = new CountDownLatch(1);
TestServiceRegistry.getInstance().setServiceInstanceChangedCallback((service) -> {
if (service == null) {
@@ -415,7 +441,7 @@
// Test whether the controller is notified about later release of the session or
// re-creation.
- testControllerAfterSessionIsGone(MockMediaSessionService2.ID);
+ testControllerAfterSessionIsGone(id);
}
private void testControllerAfterSessionIsGone(final String id) throws InterruptedException {
@@ -435,7 +461,6 @@
testNoInteraction();
}
-
private void testNoInteraction() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
final PlaybackListener playbackListener = (state) -> {
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
index ffa3c64..cc77a50 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.media.MediaController2.ControllerCallback;
import android.media.MediaSession2.CommandGroup;
+import android.os.Bundle;
import android.os.HandlerThread;
import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
@@ -57,6 +58,9 @@
interface TestControllerCallbackInterface {
// Currently empty. Add methods in ControllerCallback/BrowserCallback that you want to test.
+
+ // Browser specific callbacks
+ default void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {}
}
interface WaitForConnectionInterface {
diff --git a/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java b/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
index 0ee37b1..df45063 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
@@ -158,6 +158,7 @@
@Test
public void testGetMediaSessionService2Token() throws InterruptedException {
boolean foundTestSessionService = false;
+ boolean foundTestLibraryService = false;
List<SessionToken> tokens = mManager.getSessionServiceTokens();
for (int i = 0; i < tokens.size(); i++) {
SessionToken token = tokens.get(i);
@@ -167,15 +168,23 @@
assertEquals(SessionToken.TYPE_SESSION_SERVICE, token.getType());
assertNull(token.getSessionBinder());
foundTestSessionService = true;
+ } else if (mContext.getPackageName().equals(token.getPackageName())
+ && MockMediaLibraryService2.ID.equals(token.getId())) {
+ assertFalse(foundTestLibraryService);
+ assertEquals(SessionToken.TYPE_LIBRARY_SERVICE, token.getType());
+ assertNull(token.getSessionBinder());
+ foundTestLibraryService = true;
}
}
assertTrue(foundTestSessionService);
+ assertTrue(foundTestLibraryService);
}
@Test
public void testGetAllSessionTokens() throws InterruptedException {
boolean foundTestSession = false;
boolean foundTestSessionService = false;
+ boolean foundTestLibraryService = false;
List<SessionToken> tokens = mManager.getAllSessionTokens();
for (int i = 0; i < tokens.size(); i++) {
SessionToken token = tokens.get(i);
@@ -192,12 +201,18 @@
foundTestSessionService = true;
assertEquals(SessionToken.TYPE_SESSION_SERVICE, token.getType());
break;
+ case MockMediaLibraryService2.ID:
+ assertFalse(foundTestLibraryService);
+ assertEquals(SessionToken.TYPE_LIBRARY_SERVICE, token.getType());
+ foundTestLibraryService = true;
+ break;
default:
fail("Unexpected session " + token + " exists in the package");
}
}
assertTrue(foundTestSession);
assertTrue(foundTestSessionService);
+ assertTrue(foundTestLibraryService);
}
// Ensures if the session creation/release is notified to the server.
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
new file mode 100644
index 0000000..7a16127
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
@@ -0,0 +1,99 @@
+/*
+* Copyright 2018 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 android.media;
+
+import static junit.framework.Assert.fail;
+
+import android.content.Context;
+import android.media.MediaSession2.CommandGroup;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.TestUtils.SyncHandler;
+import android.os.Bundle;
+import android.os.Process;
+import android.service.media.MediaBrowserService.BrowserRoot;
+
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * Mock implementation of {@link MediaLibraryService2} for testing.
+ */
+public class MockMediaLibraryService2 extends MediaLibraryService2 {
+ // Keep in sync with the AndroidManifest.xml
+ public static final String ID = "TestLibrary";
+
+ public static final String ROOT_ID = "rootId";
+ public static final Bundle EXTRA = new Bundle();
+ static {
+ EXTRA.putString(ROOT_ID, ROOT_ID);
+ }
+ @GuardedBy("MockMediaLibraryService2.class")
+ private static SessionToken sToken;
+
+ private MediaLibrarySession mSession;
+
+ @Override
+ public MediaLibrarySession onCreateSession(String sessionId) {
+ final MockPlayer player = new MockPlayer(1);
+ SyncHandler handler = (SyncHandler) TestServiceRegistry.getInstance().getHandler();
+ try {
+ handler.postAndSync(() -> {
+ TestLibrarySessionCallback callback = new TestLibrarySessionCallback();
+ mSession = new MediaLibrarySessionBuilder(
+ MockMediaLibraryService2.this, player, callback)
+ .setId(sessionId).build();
+ });
+ } catch (InterruptedException e) {
+ fail(e.toString());
+ }
+ return mSession;
+ }
+
+ @Override
+ public void onDestroy() {
+ TestServiceRegistry.getInstance().cleanUp();
+ super.onDestroy();
+ }
+
+ public static SessionToken getToken(Context context) {
+ synchronized (MockMediaLibraryService2.class) {
+ if (sToken == null) {
+ sToken = new SessionToken(SessionToken.TYPE_LIBRARY_SERVICE,
+ context.getPackageName(), ID,
+ MockMediaLibraryService2.class.getName(), null);
+ }
+ return sToken;
+ }
+ }
+
+ private class TestLibrarySessionCallback extends MediaLibrarySessionCallback {
+ @Override
+ public CommandGroup onConnect(ControllerInfo controller) {
+ if (Process.myUid() != controller.getUid()) {
+ // It's system app wants to listen changes. Ignore.
+ return super.onConnect(controller);
+ }
+ TestServiceRegistry.getInstance().setServiceInstance(
+ MockMediaLibraryService2.this, controller);
+ return super.onConnect(controller);
+ }
+
+ @Override
+ public BrowserRoot onGetRoot(ControllerInfo controller, Bundle rootHints) {
+ return new BrowserRoot(ROOT_ID, EXTRA);
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
index e4a7485..9cf4911 100644
--- a/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
+++ b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
@@ -18,9 +18,14 @@
import static junit.framework.Assert.fail;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
import android.media.MediaSession2.ControllerInfo;
import android.media.MediaSession2.SessionCallback;
import android.media.TestUtils.SyncHandler;
+import android.media.session.PlaybackState;
import android.os.Process;
/**
@@ -29,7 +34,13 @@
public class MockMediaSessionService2 extends MediaSessionService2 {
// Keep in sync with the AndroidManifest.xml
public static final String ID = "TestSession";
- public MediaSession2 mSession;
+
+ private static final String DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID = "media_session_service";
+ private static final int DEFAULT_MEDIA_NOTIFICATION_ID = 1001;
+
+ private NotificationChannel mDefaultNotificationChannel;
+ private MediaSession2 mSession;
+ private NotificationManager mNotificationManager;
@Override
public MediaSession2 onCreateSession(String sessionId) {
@@ -49,6 +60,7 @@
@Override
public void onCreate() {
super.onCreate();
+ mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
}
@Override
@@ -57,6 +69,23 @@
super.onDestroy();
}
+ @Override
+ public MediaNotification onUpdateNotification(PlaybackState state) {
+ if (mDefaultNotificationChannel == null) {
+ mDefaultNotificationChannel = new NotificationChannel(
+ DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID,
+ DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID,
+ NotificationManager.IMPORTANCE_DEFAULT);
+ mNotificationManager.createNotificationChannel(mDefaultNotificationChannel);
+ }
+ Notification notification = new Notification.Builder(
+ this, DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID)
+ .setContentTitle(getPackageName())
+ .setContentText("Playback state: " + state.getState())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon).build();
+ return MediaNotification.create(DEFAULT_MEDIA_NOTIFICATION_ID, notification);
+ }
+
private class MySessionCallback extends SessionCallback {
@Override
public MediaSession2.CommandGroup onConnect(ControllerInfo controller) {
diff --git a/packages/MediaComponents/test/src/android/media/TestUtils.java b/packages/MediaComponents/test/src/android/media/TestUtils.java
index 0cca12c..1372f01 100644
--- a/packages/MediaComponents/test/src/android/media/TestUtils.java
+++ b/packages/MediaComponents/test/src/android/media/TestUtils.java
@@ -19,11 +19,13 @@
import android.content.Context;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -47,6 +49,14 @@
return new PlaybackState.Builder().setState(state, 0, 1.0f).build();
}
+ /**
+ * Finds the session with id in this test package.
+ *
+ * @param context
+ * @param id
+ * @return
+ */
+ // TODO(jaewan): Currently not working.
public static SessionToken getServiceToken(Context context, String id) {
MediaSessionManager manager =
(MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
@@ -63,6 +73,33 @@
}
/**
+ * Compares contents of two bundles.
+ *
+ * @param a a bundle
+ * @param b another bundle
+ * @return {@code true} if two bundles are the same. {@code false} otherwise. This may be
+ * incorrect if any bundle contains a bundle.
+ */
+ public static boolean equals(Bundle a, Bundle b) {
+ if (a == b) {
+ return true;
+ }
+ if (a == null || b == null) {
+ return false;
+ }
+ if (!a.keySet().containsAll(b.keySet())
+ || !b.keySet().containsAll(a.keySet())) {
+ return false;
+ }
+ for (String key : a.keySet()) {
+ if (!Objects.equals(a.get(key), b.get(key))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Handler that always waits until the Runnable finishes.
*/
public static class SyncHandler extends Handler {