Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dc71b3cc authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "MediaSession2: Allow setting session callback for test session services"

parents 4d4a2704 32ecc4f7
Loading
Loading
Loading
Loading
+30 −20
Original line number Diff line number Diff line
@@ -21,9 +21,11 @@ import android.content.Context;
import android.content.Intent;
import android.media.MediaPlayerInterface.PlaybackListener;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
import android.media.MediaSession2.PlaylistParams;
import android.media.MediaSession2.SessionCallback;
import android.media.TestServiceRegistry.SessionCallbackProxy;
import android.media.TestUtils.SyncHandler;
import android.net.Uri;
import android.os.Bundle;
@@ -34,6 +36,7 @@ import android.os.ResultReceiver;
import android.support.test.filters.FlakyTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.text.TextUtils;

import org.junit.After;
import org.junit.Before;
@@ -618,24 +621,32 @@ public class MediaController2Test extends MediaSession2TestBase {
    @Ignore
    @Test
    public void testConnectToService_sessionService() throws InterruptedException {
        connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
        testConnectToService();
        testConnectToService(MockMediaSessionService2.ID);
    }

    // TODO(jaewan): Reenable when session manager detects app installs
    @Ignore
    @Test
    public void testConnectToService_libraryService() throws InterruptedException {
        connectToService(TestUtils.getServiceToken(mContext, MockMediaLibraryService2.ID));
        testConnectToService();
        testConnectToService(MockMediaLibraryService2.ID);
    }

    public void testConnectToService() throws InterruptedException {
        TestServiceRegistry serviceInfo = TestServiceRegistry.getInstance();
        ControllerInfo info = serviceInfo.getOnConnectControllerInfo();
        assertEquals(mContext.getPackageName(), info.getPackageName());
        assertEquals(Process.myUid(), info.getUid());
        assertFalse(info.isTrusted());
    public void testConnectToService(String id) throws InterruptedException {
        final CountDownLatch latch = new CountDownLatch(1);
        final SessionCallbackProxy proxy = new SessionCallbackProxy(mContext) {
            @Override
            public CommandGroup onConnect(ControllerInfo controller) {
                if (Process.myUid() == controller.getUid()) {
                    assertEquals(mContext.getPackageName(), controller.getPackageName());
                    assertFalse(controller.isTrusted());
                    latch.countDown();;
                }
                return super.onConnect(controller);
            }
        };
        TestServiceRegistry.getInstance().setSessionCallbackProxy(proxy);
        mController = createController(TestUtils.getServiceToken(mContext, id));
        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));

        // Test command from controller to session service
        mController.play();
@@ -701,27 +712,26 @@ public class MediaController2Test extends MediaSession2TestBase {
    @Ignore
    @Test
    public void testClose_sessionService() throws InterruptedException {
        connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
        testCloseFromService();
        testCloseFromService(MockMediaSessionService2.ID);
    }

    // TODO(jaewan): Reenable when session manager detects app installs
    @Ignore
    @Test
    public void testClose_libraryService() throws InterruptedException {
        connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
        testCloseFromService();
        testCloseFromService(MockMediaLibraryService2.ID);
    }

    private void testCloseFromService() throws InterruptedException {
        final String id = mController.getSessionToken().getId();
    private void testCloseFromService(String id) throws InterruptedException {
        final CountDownLatch latch = new CountDownLatch(1);
        TestServiceRegistry.getInstance().setServiceInstanceChangedCallback((service) -> {
            if (service == null) {
                // Destroying..
        final SessionCallbackProxy proxy = new SessionCallbackProxy(mContext) {
            @Override
            public void onServiceDestroyed() {
                latch.countDown();
            }
        });
        };
        TestServiceRegistry.getInstance().setSessionCallbackProxy(proxy);
        mController = createController(TestUtils.getServiceToken(mContext, id));
        mController.close();
        // Wait until close triggers onDestroy() of the session service.
        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+24 −17
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import static org.junit.Assert.assertEquals;
import android.content.Context;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
import android.media.TestServiceRegistry.SessionCallbackProxy;
import android.media.TestUtils.SyncHandler;
import android.os.Bundle;
import android.os.Process;
@@ -32,6 +33,8 @@ import java.io.FileDescriptor;
import java.util.ArrayList;
import java.util.List;

import java.util.concurrent.Executor;

import javax.annotation.concurrent.GuardedBy;

/**
@@ -74,20 +77,27 @@ public class MockMediaLibraryService2 extends MediaLibraryService2 {
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        TestServiceRegistry.getInstance().setServiceInstance(this);
    }

    @Override
    public MediaLibrarySession onCreateSession(String sessionId) {
        final MockPlayer player = new MockPlayer(1);
        final SyncHandler handler = (SyncHandler) TestServiceRegistry.getInstance().getHandler();
        try {
            handler.postAndSync(() -> {
                TestLibrarySessionCallback callback = new TestLibrarySessionCallback();
                mSession = new MediaLibrarySessionBuilder(MockMediaLibraryService2.this,
                        player, (runnable) -> handler.post(runnable), callback)
                        .setId(sessionId).build();
            });
        } catch (InterruptedException e) {
            fail(e.toString());
        }
        final Executor executor = (runnable) -> handler.post(runnable);
        SessionCallbackProxy sessionCallbackProxy = TestServiceRegistry.getInstance()
                .getSessionCallbackProxy();
        if (sessionCallbackProxy == null) {
            // Ensures non-null
            sessionCallbackProxy = new SessionCallbackProxy(this) {};
        }
        TestLibrarySessionCallback callback =
                new TestLibrarySessionCallback(sessionCallbackProxy);
        mSession = new MediaLibrarySessionBuilder(MockMediaLibraryService2.this, player,
                executor, callback).setId(sessionId).build();
        return mSession;
    }

@@ -109,19 +119,16 @@ public class MockMediaLibraryService2 extends MediaLibraryService2 {
    }

    private class TestLibrarySessionCallback extends MediaLibrarySessionCallback {
        public TestLibrarySessionCallback() {
        private final SessionCallbackProxy mCallbackProxy;

        public TestLibrarySessionCallback(SessionCallbackProxy callbackProxy) {
            super(MockMediaLibraryService2.this);
            mCallbackProxy = callbackProxy;
        }

        @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);
            return mCallbackProxy.onConnect(controller);
        }

        @Override
+26 −26
Original line number Diff line number Diff line
@@ -22,11 +22,11 @@ import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
import android.media.MediaSession2.SessionCallback;
import android.media.TestServiceRegistry.SessionCallbackProxy;
import android.media.TestUtils.SyncHandler;
import android.media.session.PlaybackState;
import android.os.Process;

import java.util.concurrent.Executor;

@@ -44,29 +44,32 @@ public class MockMediaSessionService2 extends MediaSessionService2 {
    private MediaSession2 mSession;
    private NotificationManager mNotificationManager;

    @Override
    public void onCreate() {
        super.onCreate();
        TestServiceRegistry.getInstance().setServiceInstance(this);
        mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    }

    @Override
    public MediaSession2 onCreateSession(String sessionId) {
        final MockPlayer player = new MockPlayer(1);
        final SyncHandler handler = (SyncHandler) TestServiceRegistry.getInstance().getHandler();
        final Executor executor = (runnable) -> handler.post(runnable);
        try {
            handler.postAndSync(() -> {
                mSession = new MediaSession2.Builder(MockMediaSessionService2.this, player)
                        .setSessionCallback(executor, new MySessionCallback())
                        .setId(sessionId).build();
            });
        } catch (InterruptedException e) {
            fail(e.toString());
        SessionCallbackProxy sessionCallbackProxy = TestServiceRegistry.getInstance()
                .getSessionCallbackProxy();
        if (sessionCallbackProxy == null) {
            // Ensures non-null
            sessionCallbackProxy = new SessionCallbackProxy(this) {};
        }
        TestSessionServiceCallback callback =
                new TestSessionServiceCallback(sessionCallbackProxy);
        mSession = new MediaSession2.Builder(this, player)
                .setSessionCallback(executor, callback)
                .setId(sessionId).build();
        return mSession;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    }

    @Override
    public void onDestroy() {
        TestServiceRegistry.getInstance().cleanUp();
@@ -90,20 +93,17 @@ public class MockMediaSessionService2 extends MediaSessionService2 {
        return new MediaNotification(this, DEFAULT_MEDIA_NOTIFICATION_ID, notification);
    }

    private class MySessionCallback extends SessionCallback {
        public MySessionCallback() {
    private class TestSessionServiceCallback extends SessionCallback {
        private final SessionCallbackProxy mCallbackProxy;

        public TestSessionServiceCallback(SessionCallbackProxy callbackProxy) {
            super(MockMediaSessionService2.this);
            mCallbackProxy = callbackProxy;
        }

        @Override
        public MediaSession2.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(
                    MockMediaSessionService2.this, controller);
            return super.onConnect(controller);
        public CommandGroup onConnect(ControllerInfo controller) {
            return mCallbackProxy.onConnect(controller);
        }
    }
}
+62 −37
Original line number Diff line number Diff line
@@ -18,10 +18,12 @@ package android.media;

import static org.junit.Assert.fail;

import android.content.Context;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
import android.media.TestUtils.SyncHandler;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.support.annotation.GuardedBy;

/**
@@ -31,8 +33,46 @@ import android.support.annotation.GuardedBy;
 * It only support only one service at a time.
 */
public class TestServiceRegistry {
    public interface ServiceInstanceChangedCallback {
        void OnServiceInstanceChanged(MediaSessionService2 service);
    /**
     * Proxy for both {@link MediaSession2.SessionCallback} and
     * {@link MediaLibraryService2.MediaLibrarySessionCallback}.
     */
    public static abstract class SessionCallbackProxy {
        private final Context mContext;

        /**
         * Constructor
         */
        public SessionCallbackProxy(Context context) {
            mContext = context;
        }

        public final Context getContext() {
            return mContext;
        }

        /**
         * @param controller
         * @return
         */
        public CommandGroup onConnect(ControllerInfo controller) {
            if (Process.myUid() == controller.getUid()) {
                CommandGroup commands = new CommandGroup(mContext);
                commands.addAllPredefinedCommands();
                return commands;
            }
            return null;
        }

        /**
         * Called when enclosing service is created.
         */
        public void onServiceCreated(MediaSessionService2 service) { }

        /**
         * Called when enclosing service is destroyed.
         */
        public void onServiceDestroyed() { }
    }

    @GuardedBy("TestServiceRegistry.class")
@@ -42,9 +82,7 @@ public class TestServiceRegistry {
    @GuardedBy("TestServiceRegistry.class")
    private SyncHandler mHandler;
    @GuardedBy("TestServiceRegistry.class")
    private ControllerInfo mOnConnectControllerInfo;
    @GuardedBy("TestServiceRegistry.class")
    private ServiceInstanceChangedCallback mCallback;
    private SessionCallbackProxy mCallbackProxy;

    public static TestServiceRegistry getInstance() {
        synchronized (TestServiceRegistry.class) {
@@ -61,28 +99,33 @@ public class TestServiceRegistry {
        }
    }

    public void setServiceInstanceChangedCallback(ServiceInstanceChangedCallback callback) {
    public Handler getHandler() {
        synchronized (TestServiceRegistry.class) {
            mCallback = callback;
            return mHandler;
        }
    }

    public Handler getHandler() {
    public void setSessionCallbackProxy(SessionCallbackProxy callbackProxy) {
        synchronized (TestServiceRegistry.class) {
            return mHandler;
            mCallbackProxy = callbackProxy;
        }
    }

    public SessionCallbackProxy getSessionCallbackProxy() {
        synchronized (TestServiceRegistry.class) {
            return mCallbackProxy;
        }
    }

    public void setServiceInstance(MediaSessionService2 service, ControllerInfo controller) {
    public void setServiceInstance(MediaSessionService2 service) {
        synchronized (TestServiceRegistry.class) {
            if (mService != null) {
                fail("Previous service instance is still running. Clean up manually to ensure"
                        + " previoulsy running service doesn't break current test");
            }
            mService = service;
            mOnConnectControllerInfo = controller;
            if (mCallback != null) {
                mCallback.OnServiceInstanceChanged(service);
            if (mCallbackProxy != null) {
                mCallbackProxy.onServiceCreated(service);
            }
        }
    }
@@ -93,28 +136,11 @@ public class TestServiceRegistry {
        }
    }

    public ControllerInfo getOnConnectControllerInfo() {
        synchronized (TestServiceRegistry.class) {
            return mOnConnectControllerInfo;
        }
    }


    public void cleanUp() {
        synchronized (TestServiceRegistry.class) {
            final ServiceInstanceChangedCallback callback = mCallback;
            final SessionCallbackProxy callbackProxy = mCallbackProxy;
            if (mService != null) {
                try {
                    if (mHandler.getLooper() == Looper.myLooper()) {
                        mService.getSession().close();
                    } else {
                        mHandler.postAndSync(() -> {
                mService.getSession().close();
                        });
                    }
                } catch (InterruptedException e) {
                    // No-op. Service containing session will die, but shouldn't be a huge issue.
                }
                // stopSelf() would not kill service while the binder connection established by
                // bindService() exists, and close() above will do the job instead.
                // So stopSelf() isn't really needed, but just for sure.
@@ -124,11 +150,10 @@ public class TestServiceRegistry {
            if (mHandler != null) {
                mHandler.removeCallbacksAndMessages(null);
            }
            mCallback = null;
            mOnConnectControllerInfo = null;
            mCallbackProxy = null;

            if (callback != null) {
                callback.OnServiceInstanceChanged(null);
            if (callbackProxy != null) {
                callbackProxy.onServiceDestroyed();
            }
        }
    }