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

Commit 79f5e126 authored by Hyundo Moon's avatar Hyundo Moon
Browse files

MediaSession2: Replace PlaybackState2

This CL removes PlaybackState2 and replaces it with other APIs.

Bug: 74370608, Bug: 73971431
Test: Passed CTS
Change-Id: I2eb1238261cb8db0b13282da77b92354b5104242
parent fc5cde25
Loading
Loading
Loading
Loading
+8 −4
Original line number Diff line number Diff line
@@ -31,15 +31,19 @@ import com.android.media.IMediaSession2;
// TODO(jaewan): (Post P) Handle when the playlist becomes too huge.
//               Note that ParcelledSliceList isn't a good idea for the purpose. (see: b/37493677)
oneway interface IMediaController2 {
    void onPlaybackStateChanged(in Bundle state);
    void onPlayerStateChanged(int state);
    void onPositionChanged(long eventTimeMs, long positionMs);
    void onPlaybackSpeedChanged(float speed);
    void onBufferedPositionChanged(long bufferedPositionMs);
    void onPlaylistChanged(in List<Bundle> playlist, in Bundle metadata);
    void onPlaylistMetadataChanged(in Bundle metadata);
    void onPlaylistParamsChanged(in Bundle params);
    void onPlaybackInfoChanged(in Bundle playbackInfo);

    void onConnected(IMediaSession2 sessionBinder, in Bundle commandGroup, in Bundle playbackState,
            in Bundle playbackInfo, in Bundle params, in List<Bundle> playlist,
            in PendingIntent sessionActivity);
    void onConnected(IMediaSession2 sessionBinder, in Bundle commandGroup,
            int playerState, long positionEventTimeMs, long positionMs, float playbackSpeed,
            long bufferedPositionMs, in Bundle playbackInfo, in Bundle params,
            in List<Bundle> playlist, in PendingIntent sessionActivity);
    void onDisconnected();

    void onCustomLayoutChanged(in List<Bundle> commandButtonlist);
+88 −24
Original line number Diff line number Diff line
@@ -16,7 +16,18 @@

package com.android.media;

import static android.media.MediaSession2.*;
import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME;
import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_ADD_ITEM;
import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM;
import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM;
import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST;
import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST_METADATA;
import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_MEDIA_ID;
import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_SEARCH;
import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_URI;
import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_MEDIA_ID;
import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_SEARCH;
import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_URI;

import android.app.PendingIntent;
import android.content.ComponentName;
@@ -35,7 +46,6 @@ import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.PlaylistParams;
import android.media.MediaSessionService2;
import android.media.PlaybackState2;
import android.media.Rating2;
import android.media.SessionToken2;
import android.media.update.MediaController2Provider;
@@ -73,14 +83,22 @@ public class MediaController2Impl implements MediaController2Provider {
    @GuardedBy("mLock")
    private boolean mIsReleased;
    @GuardedBy("mLock")
    private PlaybackState2 mPlaybackState;
    @GuardedBy("mLock")
    private List<MediaItem2> mPlaylist;
    @GuardedBy("mLock")
    private MediaMetadata2 mPlaylistMetadata;
    @GuardedBy("mLock")
    private PlaylistParams mPlaylistParams;
    @GuardedBy("mLock")
    private int mPlayerState;
    @GuardedBy("mLock")
    private long mPositionEventTimeMs;
    @GuardedBy("mLock")
    private long mPositionMs;
    @GuardedBy("mLock")
    private float mPlaybackSpeed;
    @GuardedBy("mLock")
    private long mBufferedPositionMs;
    @GuardedBy("mLock")
    private PlaybackInfo mPlaybackInfo;
    @GuardedBy("mLock")
    private PendingIntent mSessionActivity;
@@ -613,13 +631,6 @@ public class MediaController2Impl implements MediaController2Provider {
        */
    }

    @Override
    public PlaybackState2 getPlaybackState_impl() {
        synchronized (mLock) {
            return mPlaybackState;
        }
    }

    @Override
    public void addPlaylistItem_impl(int index, MediaItem2 item) {
        if (index < 0) {
@@ -703,26 +714,32 @@ public class MediaController2Impl implements MediaController2Provider {

    @Override
    public int getPlayerState_impl() {
        // TODO(jaewan): Implement
        return 0;
        synchronized (mLock) {
            return mPlayerState;
        }
    }

    @Override
    public long getPosition_impl() {
        // TODO(jaewan): Implement
        return 0;
        synchronized (mLock) {
            long timeDiff = System.currentTimeMillis() - mPositionEventTimeMs;
            long expectedPosition = mPositionMs + (long) (mPlaybackSpeed * timeDiff);
            return Math.max(0, expectedPosition);
        }
    }

    @Override
    public float getPlaybackSpeed_impl() {
        // TODO(jaewan): Implement
        return 0;
        synchronized (mLock) {
            return mPlaybackSpeed;
        }
    }

    @Override
    public long getBufferedPosition_impl() {
        // TODO(jaewan): Implement
        return 0;
        synchronized (mLock) {
            return mBufferedPositionMs;
        }
    }

    @Override
@@ -731,15 +748,52 @@ public class MediaController2Impl implements MediaController2Provider {
        return null;
    }

    void pushPlaybackStateChanges(final PlaybackState2 state) {
    void pushPlayerStateChanges(final int state) {
        synchronized (mLock) {
            mPlayerState = state;
        }
        mCallbackExecutor.execute(() -> {
            if (!mInstance.isConnected()) {
                return;
            }
            mCallback.onPlayerStateChanged(mInstance, state);
        });
    }

    void pushPositionChanges(final long eventTimeMs, final long positionMs) {
        synchronized (mLock) {
            mPositionEventTimeMs = eventTimeMs;
            mPositionMs = positionMs;
        }
        mCallbackExecutor.execute(() -> {
            if (!mInstance.isConnected()) {
                return;
            }
            mCallback.onPositionChanged(mInstance, eventTimeMs, positionMs);
        });
    }

    void pushPlaybackSpeedChanges(final float speed) {
        synchronized (mLock) {
            mPlaybackSpeed = speed;
        }
        mCallbackExecutor.execute(() -> {
            if (!mInstance.isConnected()) {
                return;
            }
            mCallback.onPlaybackSpeedChanged(mInstance, speed);
        });
    }

    void pushBufferedPositionChanges(final long bufferedPositionMs) {
        synchronized (mLock) {
            mPlaybackState = state;
            mBufferedPositionMs = bufferedPositionMs;
        }
        mCallbackExecutor.execute(() -> {
            if (!mInstance.isConnected()) {
                return;
            }
            mCallback.onPlaybackStateChanged(mInstance, state);
            mCallback.onBufferedPositionChanged(mInstance, bufferedPositionMs);
        });
    }

@@ -796,7 +850,13 @@ public class MediaController2Impl implements MediaController2Provider {

    // Should be used without a lock to prevent potential deadlock.
    void onConnectedNotLocked(IMediaSession2 sessionBinder,
            final CommandGroup allowedCommands, final PlaybackState2 state, final PlaybackInfo info,
            final CommandGroup allowedCommands,
            final int playerState,
            final long positionEventTimeMs,
            final long positionMs,
            final float playbackSpeed,
            final long bufferedPositionMs,
            final PlaybackInfo info,
            final PlaylistParams params, final List<MediaItem2> playlist,
            final PendingIntent sessionActivity) {
        if (DEBUG) {
@@ -821,7 +881,11 @@ public class MediaController2Impl implements MediaController2Provider {
                    return;
                }
                mAllowedCommands = allowedCommands;
                mPlaybackState = state;
                mPlayerState = playerState;
                mPositionEventTimeMs = positionEventTimeMs;
                mPositionMs = positionMs;
                mPlaybackSpeed = playbackSpeed;
                mBufferedPositionMs = bufferedPositionMs;
                mPlaybackInfo = info;
                mPlaylistParams = params;
                mPlaylist = playlist;
+54 −7
Original line number Diff line number Diff line
@@ -25,7 +25,6 @@ import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.PlaylistParams;
import android.media.PlaybackState2;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.text.TextUtils;
@@ -69,7 +68,7 @@ public class MediaController2Stub extends IMediaController2.Stub {
    }

    @Override
    public void onPlaybackStateChanged(Bundle state) throws RuntimeException {
    public void onPlayerStateChanged(int state) {
        final MediaController2Impl controller;
        try {
            controller = getController();
@@ -77,8 +76,55 @@ public class MediaController2Stub extends IMediaController2.Stub {
            Log.w(TAG, "Don't fail silently here. Highly likely a bug");
            return;
        }
        controller.pushPlaybackStateChanges(
                PlaybackState2.fromBundle(controller.getContext(), state));
        controller.pushPlayerStateChanges(state);
    }

    @Override
    public void onPositionChanged(long eventTimeMs, long positionMs) {
        final MediaController2Impl controller;
        try {
            controller = getController();
        } catch (IllegalStateException e) {
            Log.w(TAG, "Don't fail silently here. Highly likely a bug");
            return;
        }
        if (eventTimeMs < 0) {
            Log.w(TAG, "onPositionChanged(): Ignoring negative eventTimeMs");
            return;
        }
        if (positionMs < 0) {
            Log.w(TAG, "onPositionChanged(): Ignoring negative positionMs");
            return;
        }
        controller.pushPositionChanges(eventTimeMs, positionMs);
    }

    @Override
    public void onPlaybackSpeedChanged(float speed) {
        final MediaController2Impl controller;
        try {
            controller = getController();
        } catch (IllegalStateException e) {
            Log.w(TAG, "Don't fail silently here. Highly likely a bug");
            return;
        }
        controller.pushPlaybackSpeedChanges(speed);
    }

    @Override
    public void onBufferedPositionChanged(long bufferedPositionMs) {
        final MediaController2Impl controller;
        try {
            controller = getController();
        } catch (IllegalStateException e) {
            Log.w(TAG, "Don't fail silently here. Highly likely a bug");
            return;
        }
        if (bufferedPositionMs < 0) {
            Log.w(TAG, "onBufferedPositionChanged(): Ignoring negative bufferedPositionMs");
            return;
        }
        controller.pushBufferedPositionChanges(bufferedPositionMs);
    }

    @Override
@@ -163,8 +209,9 @@ public class MediaController2Stub extends IMediaController2.Stub {

    @Override
    public void onConnected(IMediaSession2 sessionBinder, Bundle commandGroup,
            Bundle playbackState, Bundle playbackInfo, Bundle playlistParams, List<Bundle>
            itemBundleList, PendingIntent sessionActivity) {
            int playerState, long positionEventTimeMs, long positionMs, float playbackSpeed,
            long bufferedPositionMs, Bundle playbackInfo, Bundle playlistParams,
            List<Bundle> itemBundleList, PendingIntent sessionActivity) {
        final MediaController2Impl controller = mController.get();
        if (controller == null) {
            if (DEBUG) {
@@ -185,7 +232,7 @@ public class MediaController2Stub extends IMediaController2.Stub {
        }
        controller.onConnectedNotLocked(sessionBinder,
                CommandGroup.fromBundle(context, commandGroup),
                PlaybackState2.fromBundle(context, playbackState),
                playerState, positionEventTimeMs, positionMs, playbackSpeed, bufferedPositionMs,
                PlaybackInfoImpl.fromBundle(context, playbackInfo),
                PlaylistParams.fromBundle(context, playlistParams),
                itemList, sessionActivity);
+74 −41
Original line number Diff line number Diff line
@@ -52,7 +52,6 @@ import android.media.MediaSession2.PlaylistParams.RepeatMode;
import android.media.MediaSession2.PlaylistParams.ShuffleMode;
import android.media.MediaSession2.SessionCallback;
import android.media.MediaSessionService2;
import android.media.PlaybackState2;
import android.media.SessionToken2;
import android.media.VolumeProvider2;
import android.media.session.MediaSessionManager;
@@ -248,11 +247,11 @@ public class MediaSession2Impl implements MediaSession2Provider {
                oldAgent.unregisterPlaylistEventCallback(mPlaylistEventCallback);
            }
        }
        // TODO(jaewan): Notify controllers about the change in the media player base (b/74370608)
        //               Note that notification will be done indirectly by telling player state,
        //               position, buffered position, etc.

        if (oldPlayer != null) {
            mSessionStub.notifyPlaybackInfoChanged(info);
        notifyPlaybackStateChangedNotLocked(mInstance.getPlaybackState());
            notifyPlayerUpdatedNotLocked(oldPlayer);
        }
    }

    private PlaybackInfo createPlaybackInfo(VolumeProvider2 volumeProvider, AudioAttributes attrs) {
@@ -676,12 +675,6 @@ public class MediaSession2Impl implements MediaSession2Provider {
            return;
        }
        mCallbacks.put(callback, executor);
        // TODO: Uncomment or remove
        /*
        // TODO(jaewan): Double check if we need this.
        final PlaybackState2 state = getInstance().getPlaybackState();
        executor.execute(() -> callback.onPlaybackStateChanged(state));
        */
    }

    @Override
@@ -693,25 +686,6 @@ public class MediaSession2Impl implements MediaSession2Provider {
        mCallbacks.remove(callback);
    }

    @Override
    public PlaybackState2 getPlaybackState_impl() {
        ensureCallingThread();
        // TODO: Uncomment or remove
        /*
        final MediaPlayerBase player = mPlayer;
        if (player != null) {
           // TODO(jaewan): Is it safe to be called on any thread?
            //               Otherwise MediaSession2 should cache the result from listener.
            // TODO implement
            //return player.getPlaybackState();
            return null;
        } else if (DEBUG) {
            Log.d(TAG, "API calls after the close()", new IllegalStateException());
        }
        */
        return null;
    }

    @Override
    public void notifyError_impl(int errorCode, Bundle extras) {
        // TODO(jaewan): Implement
@@ -761,9 +735,11 @@ public class MediaSession2Impl implements MediaSession2Provider {
        mSessionStub.notifyPlaylistMetadataChangedNotLocked(metadata);
    }

    private void notifyPlaybackStateChangedNotLocked(final PlaybackState2 state) {
    private void notifyPlayerUpdatedNotLocked(MediaPlayerBase oldPlayer) {
        ArrayMap<PlayerEventCallback, Executor> callbacks = new ArrayMap<>();
        MediaPlayerBase player;
        synchronized (mLock) {
            player = mPlayer;
            callbacks.putAll(mCallbacks);
        }
        // Notify to callbacks added directly to this session
@@ -774,7 +750,26 @@ public class MediaSession2Impl implements MediaSession2Provider {
            //executor.execute(() -> callback.onPlaybackStateChanged(state));
        }
        // Notify to controllers as well.
        mSessionStub.notifyPlaybackStateChangedNotLocked(state);
        final int state = player.getPlayerState();
        if (state != oldPlayer.getPlayerState()) {
            mSessionStub.notifyPlayerStateChangedNotLocked(state);
        }

        final long currentTimeMs = System.currentTimeMillis();
        final long position = player.getCurrentPosition();
        if (position != oldPlayer.getCurrentPosition()) {
            mSessionStub.notifyPositionChangedNotLocked(currentTimeMs, position);
        }

        final float speed = player.getPlaybackSpeed();
        if (speed != oldPlayer.getPlaybackSpeed()) {
            mSessionStub.notifyPlaybackSpeedChangedNotLocked(speed);
        }

        final long bufferedPosition = player.getBufferedPosition();
        if (bufferedPosition != oldPlayer.getBufferedPosition()) {
            mSessionStub.notifyBufferedPositionChangedNotLocked(bufferedPosition);
        }
    }

    private void notifyErrorNotLocked(String mediaId, int what, int extra) {
@@ -843,26 +838,64 @@ public class MediaSession2Impl implements MediaSession2Provider {

        @Override
        public void onCurrentDataSourceChanged(MediaPlayerBase mpb, DataSourceDesc dsd) {
            super.onCurrentDataSourceChanged(mpb, dsd);
            // TODO(jaewan): Handle this b/74370608
            MediaSession2Impl session = getSession();
            if (session == null) {
                return;
            }
            session.getCallbackExecutor().execute(() -> {
                // TODO (jaewan): Convert dsd to MediaItem (b/74506462)
                // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
                session.getCallback().onCurrentMediaItemChanged(
                        session.getInstance(), mpb, null /* MediaItem */);
            });
        }

        @Override
        public void onMediaPrepared(MediaPlayerBase mpb, DataSourceDesc dsd) {
            super.onMediaPrepared(mpb, dsd);
            // TODO(jaewan): Handle this b/74370608
            MediaSession2Impl session = getSession();
            if (session == null) {
                return;
            }
            session.getCallbackExecutor().execute(() -> {
                // TODO (jaewan): Convert dsd to MediaItem (b/74506462)
                // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
                session.getCallback().onMediaPrepared(
                        session.getInstance(), mpb, null /* MediaItem */);
            });
        }

        @Override
        public void onPlayerStateChanged(MediaPlayerBase mpb, int state) {
            super.onPlayerStateChanged(mpb, state);
            // TODO(jaewan): Handle this b/74370608
            MediaSession2Impl session = getSession();
            if (session == null) {
                return;
            }
            session.getCallbackExecutor().execute(() -> {
                session.getCallback().onPlayerStateChanged(session.getInstance(), mpb, state);
                session.getSessionStub().notifyPlayerStateChangedNotLocked(state);
            });
        }

        @Override
        public void onBufferingStateChanged(MediaPlayerBase mpb, DataSourceDesc dsd, int state) {
            super.onBufferingStateChanged(mpb, dsd, state);
            // TODO(jaewan): Handle this b/74370608
            MediaSession2Impl session = getSession();
            if (session == null) {
                return;
            }
            session.getCallbackExecutor().execute(() -> {
                // TODO (jaewan): Convert dsd to MediaItem (b/74506462)
                // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
                session.getCallback().onBufferingStateChanged(
                        session.getInstance(), mpb, null /* MediaItem */, state);
            });
        }

        private MediaSession2Impl getSession() {
            final MediaSession2Impl session = mSession.get();
            if (session == null && DEBUG) {
                Log.d(TAG, "Session is closed", new IllegalStateException());
            }
            return session;
        }
    }

+60 −10
Original line number Diff line number Diff line
@@ -22,13 +22,13 @@ import android.media.MediaController2;
import android.media.MediaItem2;
import android.media.MediaLibraryService2.LibraryRoot;
import android.media.MediaMetadata2;
import android.media.MediaPlayerBase;
import android.media.MediaSession2;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
import android.media.MediaSession2.PlaylistParams;
import android.media.PlaybackState2;
import android.media.Rating2;
import android.media.VolumeProvider2;
import android.net.Uri;
@@ -259,9 +259,11 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
                //       that events here are notified after the onConnected() because
                //       IMediaController2 is oneway (i.e. async call) and Stub will
                //       use thread poll for incoming calls.
                // TODO(jaewan): Should we protect getting playback state?
                final PlaybackState2 state = session.getInstance().getPlaybackState();
                final Bundle playbackStateBundle = (state != null) ? state.toBundle() : null;
                final int playerState = session.getInstance().getPlayerState();
                final long positionEventTimeMs = System.currentTimeMillis();
                final long positionMs = session.getInstance().getCurrentPosition();
                final float playbackSpeed = session.getInstance().getPlaybackSpeed();
                final long bufferedPositionMs = session.getInstance().getBufferedPosition();
                final Bundle playbackInfoBundle = ((MediaController2Impl.PlaybackInfoImpl)
                        session.getPlaybackInfo().getProvider()).toBundle();
                final PlaylistParams params = session.getInstance().getPlaylistParams();
@@ -293,9 +295,10 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
                    return;
                }
                try {
                    caller.onConnected(MediaSession2Stub.this,
                            allowedCommands.toBundle(), playbackStateBundle, playbackInfoBundle,
                            paramsBundle, playlistBundle, sessionActivity);
                    caller.onConnected(MediaSession2Stub.this, allowedCommands.toBundle(),
                            playerState, positionEventTimeMs, positionMs, playbackSpeed,
                            bufferedPositionMs, playbackInfoBundle, paramsBundle, playlistBundle,
                            sessionActivity);
                } catch (RemoteException e) {
                    // Controller may be died prematurely.
                    // TODO(jaewan): Handle here.
@@ -1082,7 +1085,7 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
    }

    // Should be used without a lock to prevent potential deadlock.
    public void notifyPlaybackStateChangedNotLocked(PlaybackState2 state) {
    public void notifyPlayerStateChangedNotLocked(int state) {
        final List<ControllerInfo> list = getControllers();
        for (int i = 0; i < list.size(); i++) {
            final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
@@ -1090,8 +1093,55 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
                return;
            }
            try {
                final Bundle bundle = state != null ? state.toBundle() : null;
                controllerBinder.onPlaybackStateChanged(bundle);
                controllerBinder.onPlayerStateChanged(state);
            } catch (RemoteException e) {
                Log.w(TAG, "Controller is gone", e);
                // TODO(jaewan): What to do when the controller is gone?
            }
        }
    }

    public void notifyPositionChangedNotLocked(long eventTimeMs, long positionMs) {
        final List<ControllerInfo> list = getControllers();
        for (int i = 0; i < list.size(); i++) {
            final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
            if (controllerBinder == null) {
                return;
            }
            try {
                controllerBinder.onPositionChanged(eventTimeMs, positionMs);
            } catch (RemoteException e) {
                Log.w(TAG, "Controller is gone", e);
                // TODO(jaewan): What to do when the controller is gone?
            }
        }
    }

    public void notifyPlaybackSpeedChangedNotLocked(float speed) {
        final List<ControllerInfo> list = getControllers();
        for (int i = 0; i < list.size(); i++) {
            final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
            if (controllerBinder == null) {
                return;
            }
            try {
                controllerBinder.onPlaybackSpeedChanged(speed);
            } catch (RemoteException e) {
                Log.w(TAG, "Controller is gone", e);
                // TODO(jaewan): What to do when the controller is gone?
            }
        }
    }

    public void notifyBufferedPositionChangedNotLocked(long bufferedPositionMs) {
        final List<ControllerInfo> list = getControllers();
        for (int i = 0; i < list.size(); i++) {
            final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
            if (controllerBinder == null) {
                return;
            }
            try {
                controllerBinder.onBufferedPositionChanged(bufferedPositionMs);
            } catch (RemoteException e) {
                Log.w(TAG, "Controller is gone", e);
                // TODO(jaewan): What to do when the controller is gone?
Loading