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

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

MediaSession2: Public APIs for MediaSession2 and MediaController2

Test: Run MediaComponents tests once
Change-Id: Iaf643434e9e47b0933c7740fc670346f779a5a15
parent 254d783c
Loading
Loading
Loading
Loading
+122 −55
Original line number Diff line number Diff line
@@ -16,12 +16,15 @@

package com.android.media;

import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.media.IMediaSession2;
import android.media.IMediaSession2Callback;
import android.media.MediaController2.PlaybackInfo;
import android.media.MediaItem2;
import android.media.MediaSession2;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
@@ -29,14 +32,19 @@ import android.media.MediaSession2.CommandGroup;
import android.media.MediaController2;
import android.media.MediaController2.ControllerCallback;
import android.media.MediaPlayerBase;
import android.media.MediaSession2.PlaylistParam;
import android.media.MediaSessionService2;
import android.media.PlaybackState2;
import android.media.Rating2;
import android.media.SessionToken;
import android.media.session.PlaybackState;
import android.media.update.MediaController2Provider;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.support.annotation.GuardedBy;
import android.util.Log;

@@ -257,69 +265,128 @@ public class MediaController2Impl implements MediaController2Provider {
        }
    }

    //////////////////////////////////////////////////////////////////////////////////////
    // TODO(jaewan): Implement follows
    //////////////////////////////////////////////////////////////////////////////////////
    @Override
    public PlaybackState getPlaybackState_impl() {
        final IMediaSession2 binder = mSessionBinder;
        if (binder != null) {
            try {
                return binder.getPlaybackState();
            } catch (RemoteException e) {
                Log.w(TAG, "Cannot connect to the service or the session is gone", e);
    public PendingIntent getSessionActivity_impl() {
        // TODO(jaewan): Implement
        return null;
    }
        } else {
            Log.w(TAG, "Session isn't active", new IllegalStateException());

    @Override
    public int getRatingType_impl() {
        // TODO(jaewan): Implement
        return 0;
    }

    @Override
    public void setVolumeTo_impl(int value, int flags) {
        // TODO(jaewan): Implement
    }
        // TODO(jaewan): What to return for error case?

    @Override
    public void adjustVolume_impl(int direction, int flags) {
        // TODO(jaewan): Implement
    }

    @Override
    public PlaybackInfo getPlaybackInfo_impl() {
        // TODO(jaewan): Implement
        return null;
    }

    @Override
    public void addPlaybackListener_impl(
            MediaPlayerBase.PlaybackListener listener, Handler handler) {
        if (listener == null) {
            throw new IllegalArgumentException("listener shouldn't be null");
    public void prepareFromUri_impl(Uri uri, Bundle extras) {
        // TODO(jaewan): Implement
    }
        if (handler == null) {
            throw new IllegalArgumentException("handler shouldn't be null");

    @Override
    public void prepareFromSearch_impl(String query, Bundle extras) {
        // TODO(jaewan): Implement
    }
        boolean registerCallback;
        synchronized (mLock) {
            if (PlaybackListenerHolder.contains(mPlaybackListeners, listener)) {
                throw new IllegalArgumentException("listener is already added. Ignoring.");

    @Override
    public void prepareMediaId_impl(String mediaId, Bundle extras) {
        // TODO(jaewan): Implement
    }
            registerCallback = mPlaybackListeners.isEmpty();
            mPlaybackListeners.add(new PlaybackListenerHolder(listener, handler));

    @Override
    public void playFromSearch_impl(String query, Bundle extras) {
        // TODO(jaewan): Implement
    }
        if (registerCallback) {
            registerCallbackForPlaybackNotLocked();

    @Override
    public void playFromUri_impl(String uri, Bundle extras) {
        // TODO(jaewan): Implement
    }

    @Override
    public void playFromMediaId_impl(String mediaId, Bundle extras) {
        // TODO(jaewan): Implement
    }

    @Override
    public void removePlaybackListener_impl(MediaPlayerBase.PlaybackListener listener) {
        if (listener == null) {
            throw new IllegalArgumentException("listener shouldn't be null");
    public void setRating_impl(Rating2 rating) {
        // TODO(jaewan): Implement
    }
        boolean unregisterCallback;
        synchronized (mLock) {
            int idx = PlaybackListenerHolder.indexOf(mPlaybackListeners, listener);
            if (idx >= 0) {
                mPlaybackListeners.get(idx).removeCallbacksAndMessages(null);
                mPlaybackListeners.remove(idx);

    @Override
    public void sendCustomCommand_impl(Command command, Bundle args, ResultReceiver cb) {
        // TODO(jaewan): Implement
    }
            unregisterCallback = mPlaybackListeners.isEmpty();

    @Override
    public List<MediaItem2> getPlaylist_impl() {
        // TODO(jaewan): Implement
        return null;
    }
        if (unregisterCallback) {
            final IMediaSession2 binder = mSessionBinder;
            if (binder != null) {
                // Lazy unregister
                try {
                    binder.unregisterCallback(mSessionCallbackStub, CALLBACK_FLAG_PLAYBACK);
                } catch (RemoteException e) {
                    Log.e(TAG, "Cannot connect to the service or the session is gone", e);

    @Override
    public void prepare_impl() {
        // TODO(jaewan): Implement
    }

    @Override
    public void fastForward_impl() {
        // TODO(jaewan): Implement
    }

    @Override
    public void rewind_impl() {
        // TODO(jaewan): Implement
    }

    @Override
    public void seekTo_impl(long pos) {
        // TODO(jaewan): Implement
    }

    @Override
    public void setCurrentPlaylistItem_impl(int index) {
        // TODO(jaewan): Implement
    }

    @Override
    public PlaybackState2 getPlaybackState_impl() {
        // TODO(jaewan): Implement
        return null;
    }

    @Override
    public void removePlaylistItem_impl(MediaItem2 index) {
        // TODO(jaewan): Implement
    }

    @Override
    public void addPlaylistItem_impl(int index, MediaItem2 item) {
    // TODO(jaewan): Implement
    }

    @Override
    public PlaylistParam getPlaylistParam_impl() {
        // TODO(jaewan): Implement
        return null;
    }

    ///////////////////////////////////////////////////
+66 −38
Original line number Diff line number Diff line
@@ -22,20 +22,26 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.media.AudioAttributes;
import android.media.IMediaSession2Callback;
import android.media.MediaController2;
import android.media.MediaItem2;
import android.media.MediaPlayerBase;
import android.media.MediaSession2;
import android.media.MediaSession2.Builder;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
import android.media.MediaSession2.PlaylistParam;
import android.media.MediaSession2.SessionCallback;
import android.media.SessionToken;
import android.media.VolumeProvider;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.media.update.MediaSession2Provider;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.ResultReceiver;
import android.util.Log;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -103,7 +109,7 @@ public class MediaSession2Impl implements MediaSession2Provider {
    //               setPlayer(null). Token can be available when player is null, and
    //               controller can also attach to session.
    @Override
    public void setPlayer_impl(MediaPlayerBase player) throws IllegalArgumentException {
    public void setPlayer_impl(MediaPlayerBase player, VolumeProvider volumeProvider) throws IllegalArgumentException {
        ensureCallingThread();
        if (player == null) {
            throw new IllegalArgumentException("player shouldn't be null");
@@ -202,52 +208,74 @@ public class MediaSession2Impl implements MediaSession2Provider {
    }

    @Override
    public PlaybackState getPlaybackState_impl() {
    public void setCustomLayout_impl(ControllerInfo controller, List<CommandButton> layout) {
        ensureCallingThread();
        ensurePlayer();
        return mPlayer.getPlaybackState();
        if (controller == null) {
            throw new IllegalArgumentException("controller shouldn't be null");
        }
        if (layout == null) {
            throw new IllegalArgumentException("layout shouldn't be null");
        }
        mSessionStub.notifyCustomLayoutNotLocked(controller, layout);
    }

    //////////////////////////////////////////////////////////////////////////////////////
    // TODO(jaewan): Implement follows
    //////////////////////////////////////////////////////////////////////////////////////
    @Override
    public void addPlaybackListener_impl(
            MediaPlayerBase.PlaybackListener listener, Handler handler) {
        if (listener == null) {
            throw new IllegalArgumentException("listener shouldn't be null");
    public void setPlayer_impl(MediaPlayerBase player) {
        // TODO(jaewan): Implement
    }
        if (handler == null) {
            throw new IllegalArgumentException("handler shouldn't be null");

    @Override
    public void setAllowedCommands_impl(ControllerInfo controller, CommandGroup commands) {
        // TODO(jaewan): Implement
    }
        ensureCallingThread();
        if (PlaybackListenerHolder.contains(mListeners, listener)) {
            Log.w(TAG, "listener is already added. Ignoring.");
            return;

    @Override
    public void notifyMetadataChanged_impl() {
        // TODO(jaewan): Implement
    }
        mListeners.add(new PlaybackListenerHolder(listener, handler));

    @Override
    public void sendCustomCommand_impl(ControllerInfo controller, Command command, Bundle args,
            ResultReceiver receiver) {
        // TODO(jaewan): Implement
    }

    @Override
    public void removePlaybackListener_impl(MediaPlayerBase.PlaybackListener listener) {
        if (listener == null) {
            throw new IllegalArgumentException("listener shouldn't be null");
    public void sendCustomCommand_impl(Command command, Bundle args) {
        // TODO(jaewan): Implement
    }
        ensureCallingThread();
        int idx = PlaybackListenerHolder.indexOf(mListeners, listener);
        if (idx >= 0) {
            mListeners.get(idx).removeCallbacksAndMessages(null);
            mListeners.remove(idx);

    @Override
    public void setPlaylist_impl(List<MediaItem2> playlist, PlaylistParam param) {
        // TODO(jaewan): Implement
    }

    @Override
    public void prepare_impl() {
        // TODO(jaewan): Implement
    }

    @Override
    public void setCustomLayout_impl(ControllerInfo controller, List<CommandButton> layout) {
        ensureCallingThread();
        if (controller == null) {
            throw new IllegalArgumentException("controller shouldn't be null");
    public void fastForward_impl() {
        // TODO(jaewan): Implement
    }
        if (layout == null) {
            throw new IllegalArgumentException("layout shouldn't be null");

    @Override
    public void rewind_impl() {
        // TODO(jaewan): Implement
    }
        mSessionStub.notifyCustomLayoutNotLocked(controller, layout);

    @Override
    public void seekTo_impl(long pos) {
        // TODO(jaewan): Implement
    }

    @Override
    public void setCurrentPlaylistItem_impl(int index) {
        // TODO(jaewan): Implement
    }

    ///////////////////////////////////////////////////
+6 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.media.update;

import android.app.PendingIntent;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
@@ -30,6 +31,7 @@ import android.media.MediaSession2.SessionCallback;
import android.media.MediaSessionService2;
import android.media.IMediaSession2Callback;
import android.media.SessionToken;
import android.media.VolumeProvider;
import android.media.update.MediaBrowser2Provider;
import android.media.update.MediaControlView2Provider;
import android.media.update.MediaController2Provider;
@@ -75,7 +77,10 @@ public class ApiFactory implements StaticProvider {

    @Override
    public MediaSession2Provider createMediaSession2(MediaSession2 instance, Context context,
            MediaPlayerBase player, String id, SessionCallback callback) {
            MediaPlayerBase player, String id, SessionCallback callback,
            VolumeProvider volumeProvider, int ratingType,
            PendingIntent sessionActivity) {
        // TOOD(jaewan): Keep and handles extra parameters
        return new MediaSession2Impl(instance, context, player, id, callback);
    }

+12 −3
Original line number Diff line number Diff line
@@ -144,6 +144,8 @@ public class MediaController2Test extends MediaSession2TestBase {

    @Test
    public void testGetPlaybackState() throws InterruptedException {
        // TODO(jaewan): add equivalent test later
        /*
        final CountDownLatch latch = new CountDownLatch(1);
        final MediaPlayerBase.PlaybackListener listener = (state) -> {
            assertEquals(PlaybackState.STATE_BUFFERING, state.getState());
@@ -155,8 +157,11 @@ public class MediaController2Test extends MediaSession2TestBase {
        mPlayer.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_BUFFERING));
        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
        assertEquals(PlaybackState.STATE_BUFFERING, mController.getPlaybackState().getState());
        */
    }

    // TODO(jaewan): add equivalent test later
    /*
    @Test
    public void testAddPlaybackListener() throws InterruptedException {
        final CountDownLatch latch = new CountDownLatch(2);
@@ -192,6 +197,7 @@ public class MediaController2Test extends MediaSession2TestBase {
        mPlayer.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PLAYING));
        assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
    }
    */

    @Test
    public void testControllerCallback_onConnected() throws InterruptedException {
@@ -273,9 +279,6 @@ public class MediaController2Test extends MediaSession2TestBase {
            });
            final MediaController2 controller = createController(mSession.getToken());
            testHandler.post(() -> {
                controller.addPlaybackListener((state) -> {
                    // no-op. Just to set a binder call path from session to controller.
                }, sessionHandler);
                final PlaybackState state = createPlaybackState(PlaybackState.STATE_ERROR);
                for (int i = 0; i < 100; i++) {
                    // triggers call from session to controller.
@@ -360,6 +363,8 @@ public class MediaController2Test extends MediaSession2TestBase {
        assertTrue(mPlayer.mPlayCalled);

        // Test command from session service to controller
        // TODO(jaewan): Add equivalent tests again
        /*
        final CountDownLatch latch = new CountDownLatch(1);
        mController.addPlaybackListener((state) -> {
            assertNotNull(state);
@@ -369,6 +374,7 @@ public class MediaController2Test extends MediaSession2TestBase {
        mPlayer.notifyPlaybackState(
                TestUtils.createPlaybackState(PlaybackState.STATE_REWINDING));
        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
        */
    }

    @Test
@@ -467,10 +473,13 @@ public class MediaController2Test extends MediaSession2TestBase {
            fail("Controller shouldn't be notified about change in session after the close.");
            latch.countDown();
        };
        // TODO(jaewan): Add equivalent tests again
        /*
        mController.addPlaybackListener(playbackListener, sHandler);
        mPlayer.notifyPlaybackState(TestUtils.createPlaybackState(PlaybackState.STATE_BUFFERING));
        assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
        mController.removePlaybackListener(playbackListener);
        */
    }

    // TODO(jaewan): Add  test for service connect rejection, when we differentiate session
+6 −0
Original line number Diff line number Diff line
@@ -139,6 +139,8 @@ public class MediaSession2Test extends MediaSession2TestBase {

    @Test
    public void testPlaybackStateChangedListener() throws InterruptedException {
        // TODO(jaewan): Add equivalent tests again
        /*
        final CountDownLatch latch = new CountDownLatch(2);
        final MockPlayer player = new MockPlayer(0);
        final PlaybackListener listener = (state) -> {
@@ -164,10 +166,13 @@ public class MediaSession2Test extends MediaSession2TestBase {
        });
        player.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PAUSED));
        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
        */
    }

    @Test
    public void testBadPlayer() throws InterruptedException {
        // TODO(jaewan): Add equivalent tests again
        /*
        final CountDownLatch latch = new CountDownLatch(3); // expected call + 1
        final BadPlayer player = new BadPlayer(0);
        sHandler.postAndSync(() -> {
@@ -181,6 +186,7 @@ public class MediaSession2Test extends MediaSession2TestBase {
        });
        player.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PAUSED));
        assertFalse(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
        */
    }

    private static class BadPlayer extends MockPlayer {