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

Commit bf960686 authored by Jaewan Kim's avatar Jaewan Kim
Browse files

MediaSession2: Implements sendCustomCommand()

Bug: 72543316
Test: Run all MediaComponents tests once
Change-Id: I64fb9b26b54f6c5eb905cfe2ccca6b7368570ef2
parent e92166d6
Loading
Loading
Loading
Loading
+41 −1
Original line number Diff line number Diff line
@@ -330,7 +330,20 @@ public class MediaController2Impl implements MediaController2Provider {

    @Override
    public void sendCustomCommand_impl(Command command, Bundle args, ResultReceiver cb) {
        // TODO(jaewan): Implement
        if (command == null) {
            throw new IllegalArgumentException("command shouldn't be null");
        }
        // TODO(jaewan): Also check if the command is allowed.
        final IMediaSession2 binder = mSessionBinder;
        if (binder != null) {
            try {
                binder.sendCustomCommand(mSessionCallbackStub, command.toBundle(), args, cb);
            } catch (RemoteException e) {
                Log.w(TAG, "Cannot connect to the service or the session is gone", e);
            }
        } else {
            Log.w(TAG, "Session isn't active", new IllegalStateException());
        }
    }

    @Override
@@ -481,6 +494,17 @@ public class MediaController2Impl implements MediaController2Provider {
        }
    }

    private void onCustomCommand(final Command command, final Bundle args,
            final ResultReceiver receiver) {
        if (DEBUG) {
            Log.d(TAG, "onCustomCommand cmd=" + command);
        }
        mCallbackExecutor.execute(() -> {
            // TODO(jaewan): Double check if the controller exists.
            mCallback.onCustomCommand(command, args, receiver);
        });
    }

    // TODO(jaewan): Pull out this from the controller2, and rename it to the MediaController2Stub
    //               or MediaBrowser2Stub.
    static class MediaSession2CallbackStub extends IMediaSession2Callback.Stub {
@@ -592,6 +616,22 @@ public class MediaController2Impl implements MediaController2Provider {
            }
            browser.onCustomLayoutChanged(layout);
        }

        @Override
        public void sendCustomCommand(Bundle commandBundle, Bundle args, ResultReceiver receiver) {
            final MediaController2Impl controller;
            try {
                controller = getController();
            } catch (IllegalStateException e) {
                Log.w(TAG, "Don't fail silently here. Highly likely a bug");
                return;
            }
            Command command = Command.fromBundle(commandBundle);
            if (command == null) {
                return;
            }
            controller.onCustomCommand(command, args, receiver);
        }
    }

    // This will be called on the main thread.
+2 −2
Original line number Diff line number Diff line
@@ -327,12 +327,12 @@ public class MediaSession2Impl implements MediaSession2Provider {
    @Override
    public void sendCustomCommand_impl(ControllerInfo controller, Command command, Bundle args,
            ResultReceiver receiver) {
        // TODO(jaewan): Implement
        mSessionStub.sendCustomCommand(controller, command, args, receiver);
    }

    @Override
    public void sendCustomCommand_impl(Command command, Bundle args) {
        // TODO(jaewan): Implement
        mSessionStub.sendCustomCommand(command, args);
    }

    @Override
+65 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.support.annotation.GuardedBy;
import android.util.ArrayMap;
import android.util.Log;
@@ -230,6 +231,27 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
        });
    }

    @Override
    public void sendCustomCommand(final IMediaSession2Callback caller, final Bundle commandBundle,
            final Bundle args, final ResultReceiver receiver) {
        final MediaSession2Impl sessionImpl = getSession();
        final ControllerInfo controller = getController(caller);
        if (controller == null) {
            if (DEBUG) {
                Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
            }
            return;
        }
        sessionImpl.getCallbackExecutor().execute(() -> {
            final MediaSession2Impl session = mSession.get();
            if (session == null) {
                return;
            }
            final Command command = Command.fromBundle(commandBundle);
            session.getCallback().onCustomCommand(controller, command, args, receiver);
        });
    }

    @Override
    public void getBrowserRoot(IMediaSession2Callback caller, Bundle rootHints)
            throws RuntimeException {
@@ -268,6 +290,9 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
    }

    private ControllerInfo getController(IMediaSession2Callback caller) {
        // TODO(jaewan): Device a way to return connection-in-progress-controller
        //               to be included here, because session owner may want to send some datas
        //               while onConnected() hasn't returned.
        synchronized (mLock) {
            return mControllers.get(caller.asBinder());
        }
@@ -333,4 +358,44 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
            }
        }
    }

    public void sendCustomCommand(ControllerInfo controller, Command command, Bundle args,
            ResultReceiver receiver) {
        if (receiver != null && controller == null) {
            throw new IllegalArgumentException("Controller shouldn't be null if result receiver is"
                    + " specified");
        }
        if (command == null) {
            throw new IllegalArgumentException("command shouldn't be null");
        }
        final IMediaSession2Callback callbackBinder =
                ControllerInfoImpl.from(controller).getControllerBinder();
        if (getController(callbackBinder) == null) {
            throw new IllegalArgumentException("Controller is gone");
        }
        sendCustomCommandInternal(controller, command, args, receiver);
    }

    public void sendCustomCommand(Command command, Bundle args) {
        if (command == null) {
            throw new IllegalArgumentException("command shouldn't be null");
        }
        final List<ControllerInfo> controllers = getControllers();
        for (int i = 0; i < controllers.size(); i++) {
            sendCustomCommand(controllers.get(i), command, args, null);
        }
    }

    private void sendCustomCommandInternal(ControllerInfo controller, Command command, Bundle args,
            ResultReceiver receiver) {
        final IMediaSession2Callback callbackBinder =
                ControllerInfoImpl.from(controller).getControllerBinder();
        try {
            Bundle commandBundle = command.toBundle();
            callbackBinder.sendCustomCommand(commandBundle, args, receiver);
        } catch (RemoteException e) {
            Log.w(TAG, "Controller is gone", e);
            // TODO(jaewan): What to do when the controller is gone?
        }
    }
}
+28 −10
Original line number Diff line number Diff line
@@ -20,11 +20,14 @@ import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;

import android.annotation.Nullable;
import android.content.Context;
import android.media.MediaBrowser2.BrowserCallback;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.PlaylistParams;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
import android.support.test.filters.SmallTest;
@@ -50,17 +53,25 @@ public class MediaBrowser2Test extends MediaController2Test {

    @Override
    TestControllerInterface onCreateController(@NonNull SessionToken2 token,
            @NonNull TestControllerCallbackInterface callback) {
            @Nullable TestControllerCallbackInterface callback) {
        if (callback == null) {
            callback = new TestBrowserCallbackInterface() {};
        }
        return new TestMediaBrowser(mContext, token, new TestBrowserCallback(callback));
    }

    interface TestBrowserCallbackInterface extends TestControllerCallbackInterface {
        // Browser specific callbacks
        default void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {}
    }

    @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() {
        final TestControllerCallbackInterface callback = new TestBrowserCallbackInterface() {
            @Override
            public void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {
                assertTrue(TestUtils.equals(param, rootHints));
@@ -84,6 +95,9 @@ public class MediaBrowser2Test extends MediaController2Test {
        public final CountDownLatch disconnectLatch = new CountDownLatch(1);

        TestBrowserCallback(TestControllerCallbackInterface callbackProxy) {
            if (callbackProxy == null) {
                throw new IllegalArgumentException("Callback proxy shouldn't be null. Test bug");
            }
            mCallbackProxy = callbackProxy;
        }

@@ -104,23 +118,27 @@ public class MediaBrowser2Test extends MediaController2Test {
        @Override
        public void onPlaybackStateChanged(PlaybackState2 state) {
            super.onPlaybackStateChanged(state);
            if (mCallbackProxy != null) {
            mCallbackProxy.onPlaybackStateChanged(state);
        }
        }

        @Override
        public void onPlaylistParamsChanged(PlaylistParams params) {
            super.onPlaylistParamsChanged(params);
            if (mCallbackProxy != null) {
            mCallbackProxy.onPlaylistParamsChanged(params);
        }

        @Override
        public void onCustomCommand(Command command, Bundle args, ResultReceiver receiver) {
            super.onCustomCommand(command, args, receiver);
            mCallbackProxy.onCustomCommand(command, args, receiver);
        }

        @Override
        public void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {
            if (mCallbackProxy != null) {
                mCallbackProxy.onGetRootResult(rootHints, rootMediaId, rootExtra);
            super.onGetRootResult(rootHints, rootMediaId, rootExtra);
            if (mCallbackProxy instanceof TestBrowserCallbackInterface) {
                ((TestBrowserCallbackInterface) mCallbackProxy)
                        .onGetRootResult(rootHints, rootMediaId, rootExtra);
            }
        }

+31 −0
Original line number Diff line number Diff line
@@ -18,14 +18,17 @@ package android.media;

import android.media.MediaController2.ControllerCallback;
import android.media.MediaPlayerInterface.PlaybackListener;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.ControllerInfo;
import android.media.MediaSession2.PlaylistParams;
import android.media.MediaSession2.SessionCallback;
import android.media.TestUtils.SyncHandler;
import android.media.session.PlaybackState;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
import android.os.ResultReceiver;
import android.support.test.filters.FlakyTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -250,6 +253,34 @@ public class MediaController2Test extends MediaSession2TestBase {
        assertEquals(PlaybackState.STATE_PAUSED, mController.getPlaybackState().getState());
    }

    @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 Bundle testArgs = new Bundle();
        testArgs.putString("args", "testSendCustomAction");

        final CountDownLatch latch = new CountDownLatch(1);
        final SessionCallback callback = new SessionCallback() {
            @Override
            public void onCustomCommand(ControllerInfo controller, Command customCommand,
                    Bundle args, ResultReceiver cb) {
                super.onCustomCommand(controller, customCommand, args, cb);
                assertEquals(mContext.getPackageName(), controller.getPackageName());
                assertEquals(testCommand, customCommand);
                assertTrue(TestUtils.equals(testArgs, args));
                assertNull(cb);
                latch.countDown();
            }
        };
        mSession.close();
        mSession = new MediaSession2.Builder(mContext, mPlayer)
                .setSessionCallback(sHandlerExecutor, callback).setId(TAG).build();
        final MediaController2 controller = createController(mSession.getToken());
        controller.sendCustomCommand(testCommand, testArgs, null);
        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
    }

    @Test
    public void testControllerCallback_onConnected() throws InterruptedException {
        // createController() uses controller callback to wait until the controller becomes
Loading