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

Commit 35f4f64f authored by Jaewan Kim's avatar Jaewan Kim Committed by Android (Google) Code Review
Browse files

Merge changes from topics "session2_additem", "session2_playlistmetadata",...

Merge changes from topics "session2_additem", "session2_playlistmetadata", "session2_getplaylist" into pi-dev

* changes:
  MediaSession2: Implement add/remove/replacePlaylistItem()
  MediaSession2: Implement update/getPlaylistMetadata()
  MediaSession2: Implement get/setPlaylist()
parents b34fe171 24ab94cf
Loading
Loading
Loading
Loading
+9 −3
Original line number Diff line number Diff line
@@ -28,12 +28,12 @@ import com.android.media.IMediaSession2Callback;
 * Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
 * and holds calls from session to make session owner(s) frozen.
 */
 // 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 IMediaSession2 {
    // TODO(jaewan): add onCommand() to send private command
    // TODO(jaewan): Due to the nature of oneway calls, APIs can be called in out of order
    //               Add id for individual calls to address this.

    // TODO(jaewan): We may consider to add another binder just for the connection
    // TODO(jaewan): (Post P) We may consider to add another binder just for the connection
    //               not to expose other methods to the controller whose connection wasn't accepted.
    //               But this would be enough for now because it's the same as existing
    //               MediaBrowser and MediaBrowserService.
@@ -59,6 +59,12 @@ oneway interface IMediaSession2 {
    void playFromMediaId(IMediaSession2Callback caller, String mediaId, in Bundle extras);
    void setRating(IMediaSession2Callback caller, String mediaId, in Bundle rating);

    void setPlaylist(IMediaSession2Callback caller, in List<Bundle> playlist, in Bundle metadata);
    void updatePlaylistMetadata(IMediaSession2Callback caller, in Bundle metadata);
    void addPlaylistItem(IMediaSession2Callback caller, int index, in Bundle mediaItem);
    void removePlaylistItem(IMediaSession2Callback caller, in Bundle mediaItem);
    void replacePlaylistItem(IMediaSession2Callback caller, int index, in Bundle mediaItem);

    //////////////////////////////////////////////////////////////////////////////////////////////
    // library service specific
    //////////////////////////////////////////////////////////////////////////////////////////////
+4 −2
Original line number Diff line number Diff line
@@ -28,13 +28,15 @@ import com.android.media.IMediaSession2;
 * Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
 * and holds calls from session to make session owner(s) frozen.
 */
// 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 IMediaSession2Callback {
    void onPlaybackStateChanged(in Bundle state);
    void onPlaylistChanged(in List<Bundle> playlist);
    void onPlaylistChanged(in List<Bundle> playlist, in Bundle metadata);
    void onPlaylistMetadataChanged(in Bundle metadata);
    void onPlaylistParamsChanged(in Bundle params);
    void onPlaybackInfoChanged(in Bundle playbackInfo);

    // TODO(jaewan): Handle when the playlist becomes too huge.
    void onConnected(IMediaSession2 sessionBinder, in Bundle commandGroup, in Bundle playbackState,
            in Bundle playbackInfo, in Bundle params, in List<Bundle> playlist,
            in PendingIntent sessionActivity);
+100 −10
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.media.MediaController2;
import android.media.MediaController2.ControllerCallback;
import android.media.MediaController2.PlaybackInfo;
import android.media.MediaItem2;
import android.media.MediaMetadata2;
import android.media.MediaSession2;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
@@ -76,6 +77,8 @@ public class MediaController2Impl implements MediaController2Provider {
    @GuardedBy("mLock")
    private List<MediaItem2> mPlaylist;
    @GuardedBy("mLock")
    private MediaMetadata2 mPlaylistMetadata;
    @GuardedBy("mLock")
    private PlaylistParams mPlaylistParams;
    @GuardedBy("mLock")
    private PlaybackInfo mPlaybackInfo;
@@ -525,6 +528,51 @@ public class MediaController2Impl implements MediaController2Provider {
        }
    }

    @Override
    public void setPlaylist_impl(List<MediaItem2> list, MediaMetadata2 metadata) {
        if (list == null) {
            throw new IllegalArgumentException("list shouldn't be null");
        }
        final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_SET_LIST);
        if (binder != null) {
            List<Bundle> bundleList = new ArrayList<>();
            for (int i = 0; i < list.size(); i++) {
                bundleList.add(list.get(i).toBundle());
            }
            Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
            try {
                binder.setPlaylist(mSessionCallbackStub, bundleList, metadataBundle);
            } 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
    public MediaMetadata2 getPlaylistMetadata_impl() {
        synchronized (mLock) {
            return mPlaylistMetadata;
        }
    }

    @Override
    public void updatePlaylistMetadata_impl(MediaMetadata2 metadata) {
        final IMediaSession2 binder = getSessionBinderIfAble(
                COMMAND_CODE_PLAYLIST_SET_LIST_METADATA);
        if (binder != null) {
            Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
            try {
                binder.updatePlaylistMetadata(mSessionCallbackStub, metadataBundle);
            } 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
    public void prepare_impl() {
        sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_PREPARE);
@@ -574,32 +622,59 @@ public class MediaController2Impl implements MediaController2Provider {

    @Override
    public void addPlaylistItem_impl(int index, MediaItem2 item) {
        // TODO(jaewan): Implement (b/73149584)
        if (index < 0) {
            throw new IllegalArgumentException("index shouldn't be negative");
        }
        if (item == null) {
            throw new IllegalArgumentException("item shouldn't be null");
        }
        final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_ADD_ITEM);
        if (binder != null) {
            try {
                binder.addPlaylistItem(mSessionCallbackStub, index, item.toBundle());
            } 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
    public void removePlaylistItem_impl(MediaItem2 item) {
        // TODO(jaewan): Implement (b/73149584)
        if (item == null) {
            throw new IllegalArgumentException("item shouldn't be null");
        }
        final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_REMOVE_ITEM);
        if (binder != null) {
            try {
                binder.removePlaylistItem(mSessionCallbackStub, item.toBundle());
            } 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
    public void replacePlaylistItem_impl(int index, MediaItem2 item) {
        // TODO: Implement this (b/73149407)
        if (index < 0) {
            throw new IllegalArgumentException("index shouldn't be negative");
        }
        if (item == null) {
            throw new IllegalArgumentException("item shouldn't be null");
        }
        final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_REPLACE_ITEM);
        if (binder != null) {
            try {
                binder.replacePlaylistItem(mSessionCallbackStub, index, item.toBundle());
            } 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
@@ -692,16 +767,31 @@ public class MediaController2Impl implements MediaController2Provider {
        });
    }

    void pushPlaylistChanges(final List<MediaItem2> playlist) {
    void pushPlaylistChanges(final List<MediaItem2> playlist, final MediaMetadata2 metadata) {
        synchronized (mLock) {
            mPlaylist = playlist;
            mPlaylistMetadata = metadata;
        }
        mCallbackExecutor.execute(() -> {
            if (!mInstance.isConnected()) {
                return;
            }
                mCallback.onPlaylistChanged(mInstance, playlist);
            // TODO(jaewan): Fix public API not to take playlistAgent.
            mCallback.onPlaylistChanged(mInstance, null, playlist, metadata);
        });
    }

    public void pushPlaylistMetadataChanges(MediaMetadata2 metadata) {
        synchronized (mLock) {
            mPlaylistMetadata = metadata;
        }
        mCallbackExecutor.execute(() -> {
            if (!mInstance.isConnected()) {
                return;
            }
            // TODO(jaewan): Fix public API not to take playlistAgent.
            mCallback.onPlaylistMetadataChanged(mInstance, null, metadata);
        });
    }

    // Should be used without a lock to prevent potential deadlock.
+46 −15
Original line number Diff line number Diff line
@@ -31,21 +31,31 @@ import android.media.update.MediaItem2Provider;
import android.os.Bundle;
import android.text.TextUtils;

import java.util.UUID;

public class MediaItem2Impl implements MediaItem2Provider {
    private static final String KEY_ID = "android.media.mediaitem2.id";
    private static final String KEY_FLAGS = "android.media.mediaitem2.flags";
    private static final String KEY_METADATA = "android.media.mediaitem2.metadata";
    private static final String KEY_UUID = "android.media.mediaitem2.uuid";

    private final Context mContext;
    private final MediaItem2 mInstance;
    private final String mId;
    private final int mFlags;
    private final UUID mUUID;
    private MediaMetadata2 mMetadata;
    private DataSourceDesc mDataSourceDesc;

    // From the public API
    public MediaItem2Impl(@NonNull Context context, @NonNull String mediaId,
            @Nullable DataSourceDesc dsd, @Nullable MediaMetadata2 metadata, @Flags int flags) {
        this(context, mediaId, dsd, metadata, flags, null);
    }

    private MediaItem2Impl(@NonNull Context context, @NonNull String mediaId,
            @Nullable DataSourceDesc dsd, @Nullable MediaMetadata2 metadata, @Flags int flags,
            @Nullable UUID uuid) {
        if (mediaId == null) {
            throw new IllegalArgumentException("mediaId shouldn't be null");
        }
@@ -58,24 +68,18 @@ public class MediaItem2Impl implements MediaItem2Provider {
        mDataSourceDesc = dsd;
        mMetadata = metadata;
        mFlags = flags;
        mUUID = (uuid == null) ? UUID.randomUUID() : uuid;

        mInstance = new MediaItem2(this);
    }

    // Create anonymized version
    public MediaItem2Impl(Context context, String mediaId, MediaMetadata2 metadata,
            @Flags int flags) {
        if (mediaId == null) {
            throw new IllegalArgumentException("mediaId shouldn't be null");
    @Override
    public boolean equals_impl(Object obj) {
        if (!(obj instanceof MediaItem2)) {
            return false;
        }
        if (metadata != null && !TextUtils.equals(mediaId, metadata.getMediaId())) {
            throw new IllegalArgumentException("metadata's id should be matched with the mediaid");
        }
        mContext = context;
        mId = mediaId;
        mMetadata = metadata;
        mFlags = flags;
        mInstance = new MediaItem2(this);
        MediaItem2 other = (MediaItem2) obj;
        return mUUID.equals(((MediaItem2Impl) other.getProvider()).mUUID);
    }

    /**
@@ -90,10 +94,37 @@ public class MediaItem2Impl implements MediaItem2Provider {
        if (mMetadata != null) {
            bundle.putBundle(KEY_METADATA, mMetadata.toBundle());
        }
        bundle.putString(KEY_UUID, mUUID.toString());
        return bundle;
    }

    public static MediaItem2 fromBundle(Context context, Bundle bundle) {
    /**
     * Create a MediaItem2 from the {@link Bundle}.
     *
     * @param context A context.
     * @param bundle The bundle which was published by {@link MediaItem2#toBundle()}.
     * @return The newly created MediaItem2
     */
    public static MediaItem2 fromBundle(@NonNull Context context, @NonNull Bundle bundle) {
        if (bundle == null) {
            return null;
        }
        final String uuidString = bundle.getString(KEY_UUID);
        return fromBundle(context, bundle, UUID.fromString(uuidString));
    }

    /**
     * Create a MediaItem2 from the {@link Bundle} with the specified {@link UUID}.
     * If {@link UUID}
     * can be null for creating new.
     *
     * @param context A context.
     * @param bundle The bundle which was published by {@link MediaItem2#toBundle()}.
     * @param uuid A {@link UUID} to override. Can be {@link null} for override.
     * @return The newly created MediaItem2
     */
    static MediaItem2 fromBundle(@NonNull Context context, @NonNull Bundle bundle,
            @Nullable UUID uuid) {
        if (bundle == null) {
            return null;
        }
@@ -102,7 +133,7 @@ public class MediaItem2Impl implements MediaItem2Provider {
        final MediaMetadata2 metadata = metadataBundle != null
                ? MediaMetadata2.fromBundle(context, metadataBundle) : null;
        final int flags = bundle.getInt(KEY_FLAGS);
        return new MediaItem2Impl(context, id, metadata, flags).getInstance();
        return new MediaItem2Impl(context, id, null, metadata, flags, uuid).getInstance();
    }

    private MediaItem2 getInstance() {
+1 −1
Original line number Diff line number Diff line
@@ -228,7 +228,7 @@ public class MediaMetadata2Impl implements MediaMetadata2Provider {
    }

    public static MediaMetadata2 fromBundle(Context context, Bundle bundle) {
        return new MediaMetadata2Impl(context, bundle).getInstance();
        return (bundle == null) ? null : new MediaMetadata2Impl(context, bundle).getInstance();
    }

    public static final class BuilderImpl implements MediaMetadata2Provider.BuilderProvider {
Loading