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

Commit 3bad8ce2 authored by Jaewan Kim's avatar Jaewan Kim
Browse files

MediaSession2: Introduce MediaSession2.Command / CommandGroup

Test: Run all tests once
Change-Id: I67d2b09a68bc47a3c9b09be146e8fca6584e5755
parent 35a6aa31
Loading
Loading
Loading
Loading
+24 −46
Original line number Diff line number Diff line
@@ -22,6 +22,9 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.media.IMediaSession2;
import android.media.IMediaSession2Callback;
import android.media.MediaSession2;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaController2;
import android.media.MediaController2.ControllerCallback;
import android.media.MediaPlayerBase;
@@ -29,6 +32,7 @@ import android.media.MediaSessionService2;
import android.media.SessionToken;
import android.media.session.PlaybackState;
import android.media.update.MediaController2Provider;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
@@ -197,66 +201,38 @@ public class MediaController2Impl implements MediaController2Provider {

    @Override
    public void play_impl() {
        final IMediaSession2 binder = mSessionBinder;
        if (binder != null) {
            try {
                binder.play(mSessionCallbackStub);
            } 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());
        }
        sendCommand(MediaSession2.COMMAND_CODE_PLAYBACK_START);
    }

    @Override
    public void pause_impl() {
        final IMediaSession2 binder = mSessionBinder;
        if (binder != null) {
            try {
                binder.pause(mSessionCallbackStub);
            } 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());
        }
        sendCommand(MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE);
    }

    @Override
    public void stop_impl() {
        final IMediaSession2 binder = mSessionBinder;
        if (binder != null) {
            try {
                binder.stop(mSessionCallbackStub);
            } 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());
        }
        sendCommand(MediaSession2.COMMAND_CODE_PLAYBACK_STOP);
    }

    @Override
    public void skipToPrevious_impl() {
        final IMediaSession2 binder = mSessionBinder;
        if (binder != null) {
            try {
                binder.skipToPrevious(mSessionCallbackStub);
            } 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());
        }
        sendCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM);
    }

    @Override
    public void skipToNext_impl() {
        sendCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM);
    }

    private void sendCommand(int code) {
        // TODO(jaewan): optimization) Cache Command objects?
        Command command = new Command(code);
        // TODO(jaewan): Check if the command is in the allowed group.

        final IMediaSession2 binder = mSessionBinder;
        if (binder != null) {
            try {
                binder.skipToNext(mSessionCallbackStub);
                binder.sendCommand(mSessionCallbackStub, command.toBundle(), null);
            } catch (RemoteException e) {
                Log.w(TAG, "Cannot connect to the service or the session is gone", e);
            }
@@ -356,13 +332,14 @@ public class MediaController2Impl implements MediaController2Provider {

    // 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, long commands) {
    private void onConnectionChangedNotLocked(IMediaSession2 sessionBinder,
            CommandGroup commandGroup) {
        if (DEBUG) {
            Log.d(TAG, "onConnectionChangedNotLocked sessionBinder=" + sessionBinder);
        }
        boolean release = false;
        try {
            if (sessionBinder == null) {
            if (sessionBinder == null || commandGroup == null) {
                // Connection rejected.
                release = true;
                return;
@@ -391,7 +368,7 @@ public class MediaController2Impl implements MediaController2Provider {
            }
            // TODO(jaewan): Keep commands to prevents illegal API calls.
            mCallbackExecutor.execute(() -> {
                mCallback.onConnected(commands);
                mCallback.onConnected(commandGroup);
            });
            if (registerCallbackForPlaybackNeeded) {
                registerCallbackForPlaybackNotLocked();
@@ -431,7 +408,7 @@ public class MediaController2Impl implements MediaController2Provider {
        }

        @Override
        public void onConnectionChanged(IMediaSession2 sessionBinder, long commands)
        public void onConnectionChanged(IMediaSession2 sessionBinder, Bundle commandGroup)
                throws RuntimeException {
            final MediaController2Impl controller;
            try {
@@ -440,7 +417,8 @@ public class MediaController2Impl implements MediaController2Provider {
                Log.w(TAG, "Don't fail silently here. Highly likely a bug");
                return;
            }
            controller.onConnectionChangedNotLocked(sessionBinder, commands);
            controller.onConnectionChangedNotLocked(
                    sessionBinder, CommandGroup.fromBundle(commandGroup));
        }
    }

+38 −53
Original line number Diff line number Diff line
@@ -22,10 +22,12 @@ import android.content.Context;
import android.media.IMediaSession2;
import android.media.IMediaSession2Callback;
import android.media.MediaSession2;
import android.media.MediaSession2.CommandFlags;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
import android.media.session.PlaybackState;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -73,7 +75,7 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
                    ((ControllerInfoImpl) list.get(i).getProvider()).getControllerBinder();
            try {
                // Should be used without a lock hold to prevent potential deadlock.
                callbackBinder.onConnectionChanged(null, 0);
                callbackBinder.onConnectionChanged(null, null);
            } catch (RemoteException e) {
                // Controller is gone. Should be fine because we're destroying.
            }
@@ -110,32 +112,8 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
    }

    @Override
    public void play(IMediaSession2Callback caller) throws RemoteException {
        onCommand(caller, MediaSession2.COMMAND_FLAG_PLAYBACK_START);
    }

    @Override
    public void pause(IMediaSession2Callback caller) throws RemoteException {
        onCommand(caller, MediaSession2.COMMAND_FLAG_PLAYBACK_PAUSE);
    }

    @Override
    public void stop(IMediaSession2Callback caller) throws RemoteException {
        onCommand(caller, MediaSession2.COMMAND_FLAG_PLAYBACK_STOP);
    }

    @Override
    public void skipToPrevious(IMediaSession2Callback caller) throws RemoteException {
        onCommand(caller, MediaSession2.COMMAND_FLAG_PLAYBACK_SKIP_PREV_ITEM);
    }

    @Override
    public void skipToNext(IMediaSession2Callback caller) throws RemoteException {
        onCommand(caller, MediaSession2.COMMAND_FLAG_PLAYBACK_SKIP_NEXT_ITEM);
    }

    private void onCommand(IMediaSession2Callback caller, @CommandFlags long command)
            throws IllegalArgumentException {
    public void sendCommand(IMediaSession2Callback caller, Bundle command, Bundle args)
            throws RuntimeException {
        ControllerInfo controller = getController(caller);
        if (controller == null) {
            if (DEBUG) {
@@ -143,7 +121,7 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
            }
            return;
        }
        mCommandHandler.postCommand(controller, command);
        mCommandHandler.postCommand(controller, Command.fromBundle(command), args);
    }

    @Deprecated
@@ -247,13 +225,12 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
            switch (msg.what) {
                case MSG_CONNECT:
                    ControllerInfo request = (ControllerInfo) msg.obj;
                    long allowedCommands = session.getCallback().onConnect(request);
                    CommandGroup allowedCommands = session.getCallback().onConnect(request);
                    // Don't reject connection for the request from trusted app.
                    // Otherwise server will fail to retrieve session's information to dispatch
                    // media keys to.
                    boolean accept = (allowedCommands != 0) || request.isTrusted();
                    ControllerInfoImpl impl =
                            (ControllerInfoImpl) request.getProvider();
                    boolean accept = allowedCommands != null || request.isTrusted();
                    ControllerInfoImpl impl = (ControllerInfoImpl) request.getProvider();
                    if (accept) {
                        synchronized (mLock) {
                            mControllers.put(impl.getId(), request);
@@ -266,15 +243,16 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
                    try {
                        impl.getControllerBinder().onConnectionChanged(
                                accept ? MediaSession2Stub.this : null,
                                allowedCommands);
                                allowedCommands == null ? null : allowedCommands.toBundle());
                    } catch (RemoteException e) {
                        // Controller may be died prematurely.
                    }
                    break;
                case MSG_COMMAND:
                    CommandParam param = (CommandParam) msg.obj;
                    long command = param.command;
                    boolean accepted = session.getCallback().onCommand(param.controller, command);
                    Command command = param.command;
                    boolean accepted = session.getCallback().onCommandRequest(
                            param.controller, command);
                    if (!accepted) {
                        // Don't run rejected command.
                        if (DEBUG) {
@@ -284,19 +262,24 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
                        return;
                    }

                    // Switch cannot be used because command is long, but switch only supports
                    // int.
                    // TODO(jaewan): Replace this with the switch
                    if (command == MediaSession2.COMMAND_FLAG_PLAYBACK_START) {
                    switch (param.command.getCommandCode()) {
                        case MediaSession2.COMMAND_CODE_PLAYBACK_START:
                            session.getInstance().play();
                    } else if (command == MediaSession2.COMMAND_FLAG_PLAYBACK_PAUSE) {
                            break;
                        case MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE:
                            session.getInstance().pause();
                    } else if (command == MediaSession2.COMMAND_FLAG_PLAYBACK_STOP) {
                            break;
                        case MediaSession2.COMMAND_CODE_PLAYBACK_STOP:
                            session.getInstance().stop();
                    } else if (command == MediaSession2.COMMAND_FLAG_PLAYBACK_SKIP_PREV_ITEM) {
                            break;
                        case MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM:
                            session.getInstance().skipToPrevious();
                    } else if (command == MediaSession2.COMMAND_FLAG_PLAYBACK_SKIP_NEXT_ITEM) {
                            break;
                        case MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM:
                            session.getInstance().skipToNext();
                            break;
                        default:
                            // TODO(jaewan): Handle custom command.
                    }
                    break;
            }
@@ -306,19 +289,21 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
            obtainMessage(MSG_CONNECT, request).sendToTarget();
        }

        public void postCommand(ControllerInfo controller, @CommandFlags long command) {
            CommandParam param = new CommandParam(controller, command);
        public void postCommand(ControllerInfo controller, Command command, Bundle args) {
            CommandParam param = new CommandParam(controller, command, args);
            obtainMessage(MSG_COMMAND, param).sendToTarget();
        }
    }

    private static class CommandParam {
        public final ControllerInfo controller;
        public final @CommandFlags long command;
        public final Command command;
        public final Bundle args;

        private CommandParam(ControllerInfo controller, long command) {
        private CommandParam(ControllerInfo controller, Command command, Bundle args) {
            this.controller = controller;
            this.command = command;
            this.args = args;
        }
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -124,7 +124,7 @@ function runtest-MediaComponents() {
      if [[ "${OPTION_MIN}" != "true" ]]; then
        build_targets="${build_targets} droid"
      fi
      m ${build_targets} -j || (echo "Build failed. stop" ; break)
      m ${build_targets} -j || break

      ${adb} root
      ${adb} remount
+2 −2
Original line number Diff line number Diff line
@@ -205,8 +205,8 @@ public class MediaController2Test extends MediaSession2TestBase {
    public void testControllerCallback_sessionRejects() throws InterruptedException {
        final MediaSession2.SessionCallback sessionCallback = new SessionCallback() {
            @Override
            public long onConnect(ControllerInfo controller) {
                return 0;
            public MediaSession2.CommandGroup onConnect(ControllerInfo controller) {
                return null;
            }
        };
        sHandler.postAndSync(() -> {
+10 −10
Original line number Diff line number Diff line
@@ -209,15 +209,15 @@ public class MediaSession2Test extends MediaSession2TestBase {
        assertFalse(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
        assertFalse(mPlayer.mPauseCalled);
        assertEquals(1, callback.commands.size());
        assertEquals(MediaSession2.COMMAND_FLAG_PLAYBACK_PAUSE,
                (long) callback.commands.get(0));
        assertEquals(MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE,
                (long) callback.commands.get(0).getCommandCode());
        controller.skipToNext();
        assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
        assertTrue(mPlayer.mSkipToNextCalled);
        assertFalse(mPlayer.mPauseCalled);
        assertEquals(2, callback.commands.size());
        assertEquals(MediaSession2.COMMAND_FLAG_PLAYBACK_SKIP_NEXT_ITEM,
                (long) callback.commands.get(1));
        assertEquals(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM,
                (long) callback.commands.get(1).getCommandCode());
    }

    @Test
@@ -236,28 +236,28 @@ public class MediaSession2Test extends MediaSession2TestBase {

    public class MockOnConnectCallback extends SessionCallback {
        @Override
        public long onConnect(ControllerInfo controllerInfo) {
        public MediaSession2.CommandGroup onConnect(ControllerInfo controllerInfo) {
            if (Process.myUid() != controllerInfo.getUid()) {
                return 0;
                return null;
            }
            assertEquals(mContext.getPackageName(), controllerInfo.getPackageName());
            assertEquals(Process.myUid(), controllerInfo.getUid());
            assertFalse(controllerInfo.isTrusted());
            // Reject all
            return 0;
            return null;
        }
    }

    public class MockOnCommandCallback extends SessionCallback {
        public final ArrayList<Long> commands = new ArrayList<>();
        public final ArrayList<MediaSession2.Command> commands = new ArrayList<>();

        @Override
        public boolean onCommand(ControllerInfo controllerInfo, long command) {
        public boolean onCommandRequest(ControllerInfo controllerInfo, MediaSession2.Command command) {
            assertEquals(mContext.getPackageName(), controllerInfo.getPackageName());
            assertEquals(Process.myUid(), controllerInfo.getUid());
            assertFalse(controllerInfo.isTrusted());
            commands.add(command);
            if (command == MediaSession2.COMMAND_FLAG_PLAYBACK_PAUSE) {
            if (command.getCommandCode() == MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE) {
                return false;
            }
            return true;
Loading