1/*
2 * Copyright 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.media;
18
19import static android.media.MediaPlayerBase.BUFFERING_STATE_UNKNOWN;
20
21import android.annotation.CallbackExecutor;
22import android.annotation.NonNull;
23import android.annotation.Nullable;
24import android.app.PendingIntent;
25import android.content.Context;
26import android.media.MediaPlaylistAgent.RepeatMode;
27import android.media.MediaPlaylistAgent.ShuffleMode;
28import android.media.MediaSession2.CommandButton;
29import android.media.MediaSession2.ControllerInfo;
30import android.media.MediaSession2.ErrorCode;
31import android.media.session.MediaSessionManager;
32import android.media.update.ApiLoader;
33import android.media.update.MediaController2Provider;
34import android.media.update.MediaController2Provider.PlaybackInfoProvider;
35import android.net.Uri;
36import android.os.Bundle;
37import android.os.ResultReceiver;
38
39import java.util.List;
40import java.util.concurrent.Executor;
41
42/**
43 * @hide
44 * Allows an app to interact with an active {@link MediaSession2} or a
45 * {@link MediaSessionService2} in any status. Media buttons and other commands can be sent to
46 * the session.
47 * <p>
48 * When you're done, use {@link #close()} to clean up resources. This also helps session service
49 * to be destroyed when there's no controller associated with it.
50 * <p>
51 * When controlling {@link MediaSession2}, the controller will be available immediately after
52 * the creation.
53 * <p>
54 * When controlling {@link MediaSessionService2}, the {@link MediaController2} would be
55 * available only if the session service allows this controller by
56 * {@link MediaSession2.SessionCallback#onConnect(MediaSession2, ControllerInfo)} for the service.
57 * Wait {@link ControllerCallback#onConnected(MediaController2, SessionCommandGroup2)} or
58 * {@link ControllerCallback#onDisconnected(MediaController2)} for the result.
59 * <p>
60 * A controller can be created through token from {@link MediaSessionManager} if you hold the
61 * signature|privileged permission "android.permission.MEDIA_CONTENT_CONTROL" permission or are
62 * an enabled notification listener or by getting a {@link SessionToken2} directly the
63 * the session owner.
64 * <p>
65 * MediaController2 objects are thread-safe.
66 * <p>
67 * @see MediaSession2
68 * @see MediaSessionService2
69 */
70public class MediaController2 implements AutoCloseable {
71    /**
72     * Interface for listening to change in activeness of the {@link MediaSession2}.  It's
73     * active if and only if it has set a player.
74     */
75    public abstract static class ControllerCallback {
76        /**
77         * Called when the controller is successfully connected to the session. The controller
78         * becomes available afterwards.
79         *
80         * @param controller the controller for this event
81         * @param allowedCommands commands that's allowed by the session.
82         */
83        public void onConnected(@NonNull MediaController2 controller,
84                @NonNull SessionCommandGroup2 allowedCommands) { }
85
86        /**
87         * Called when the session refuses the controller or the controller is disconnected from
88         * the session. The controller becomes unavailable afterwards and the callback wouldn't
89         * be called.
90         * <p>
91         * It will be also called after the {@link #close()}, so you can put clean up code here.
92         * You don't need to call {@link #close()} after this.
93         *
94         * @param controller the controller for this event
95         * @param controller controller for this event
96         */
97        public void onDisconnected(@NonNull MediaController2 controller) { }
98
99        /**
100         * Called when the session set the custom layout through the
101         * {@link MediaSession2#setCustomLayout(ControllerInfo, List)}.
102         * <p>
103         * Can be called before {@link #onConnected(MediaController2, SessionCommandGroup2)} is
104         * called.
105         *
106         * @param controller the controller for this event
107         * @param layout
108         */
109        public void onCustomLayoutChanged(@NonNull MediaController2 controller,
110                @NonNull List<CommandButton> layout) { }
111
112        /**
113         * Called when the session has changed anything related with the {@link PlaybackInfo}.
114         *
115         * @param controller the controller for this event
116         * @param info new playback info
117         */
118        public void onPlaybackInfoChanged(@NonNull MediaController2 controller,
119                @NonNull PlaybackInfo info) { }
120
121        /**
122         * Called when the allowed commands are changed by session.
123         *
124         * @param controller the controller for this event
125         * @param commands newly allowed commands
126         */
127        public void onAllowedCommandsChanged(@NonNull MediaController2 controller,
128                @NonNull SessionCommandGroup2 commands) { }
129
130        /**
131         * Called when the session sent a custom command.
132         *
133         * @param controller the controller for this event
134         * @param command
135         * @param args
136         * @param receiver
137         */
138        public void onCustomCommand(@NonNull MediaController2 controller,
139                @NonNull SessionCommand2 command, @Nullable Bundle args,
140                @Nullable ResultReceiver receiver) { }
141
142        /**
143         * Called when the player state is changed.
144         *
145         * @param controller the controller for this event
146         * @param state
147         */
148        public void onPlayerStateChanged(@NonNull MediaController2 controller, int state) { }
149
150        /**
151         * Called when playback speed is changed.
152         *
153         * @param controller the controller for this event
154         * @param speed speed
155         */
156        public void onPlaybackSpeedChanged(@NonNull MediaController2 controller,
157                float speed) { }
158
159        /**
160         * Called to report buffering events for a data source.
161         * <p>
162         * Use {@link #getBufferedPosition()} for current buffering position.
163         *
164         * @param controller the controller for this event
165         * @param item the media item for which buffering is happening.
166         * @param state the new buffering state.
167         */
168        public void onBufferingStateChanged(@NonNull MediaController2 controller,
169                @NonNull MediaItem2 item, @MediaPlayerBase.BuffState int state) { }
170
171        /**
172         * Called to indicate that seeking is completed.
173         *
174         * @param controller the controller for this event.
175         * @param position the previous seeking request.
176         */
177        public void onSeekCompleted(@NonNull MediaController2 controller, long position) { }
178
179        /**
180         * Called when a error from
181         *
182         * @param controller the controller for this event
183         * @param errorCode error code
184         * @param extras extra information
185         */
186        public void onError(@NonNull MediaController2 controller, @ErrorCode int errorCode,
187                @Nullable Bundle extras) { }
188
189        /**
190         * Called when the player's currently playing item is changed
191         * <p>
192         * When it's called, you should invalidate previous playback information and wait for later
193         * callbacks.
194         *
195         * @param controller the controller for this event
196         * @param item new item
197         * @see #onBufferingStateChanged(MediaController2, MediaItem2, int)
198         */
199        // TODO(jaewan): Use this (b/74316764)
200        public void onCurrentMediaItemChanged(@NonNull MediaController2 controller,
201                @NonNull MediaItem2 item) { }
202
203        /**
204         * Called when a playlist is changed.
205         *
206         * @param controller the controller for this event
207         * @param list new playlist
208         * @param metadata new metadata
209         */
210        public void onPlaylistChanged(@NonNull MediaController2 controller,
211                @NonNull List<MediaItem2> list, @Nullable MediaMetadata2 metadata) { }
212
213        /**
214         * Called when a playlist metadata is changed.
215         *
216         * @param controller the controller for this event
217         * @param metadata new metadata
218         */
219        public void onPlaylistMetadataChanged(@NonNull MediaController2 controller,
220                @Nullable MediaMetadata2 metadata) { }
221
222        /**
223         * Called when the shuffle mode is changed.
224         *
225         * @param controller the controller for this event
226         * @param shuffleMode repeat mode
227         * @see MediaPlaylistAgent#SHUFFLE_MODE_NONE
228         * @see MediaPlaylistAgent#SHUFFLE_MODE_ALL
229         * @see MediaPlaylistAgent#SHUFFLE_MODE_GROUP
230         */
231        public void onShuffleModeChanged(@NonNull MediaController2 controller,
232                @MediaPlaylistAgent.ShuffleMode int shuffleMode) { }
233
234        /**
235         * Called when the repeat mode is changed.
236         *
237         * @param controller the controller for this event
238         * @param repeatMode repeat mode
239         * @see MediaPlaylistAgent#REPEAT_MODE_NONE
240         * @see MediaPlaylistAgent#REPEAT_MODE_ONE
241         * @see MediaPlaylistAgent#REPEAT_MODE_ALL
242         * @see MediaPlaylistAgent#REPEAT_MODE_GROUP
243         */
244        public void onRepeatModeChanged(@NonNull MediaController2 controller,
245                @MediaPlaylistAgent.RepeatMode int repeatMode) { }
246    }
247
248    /**
249     * Holds information about the current playback and how audio is handled for
250     * this session.
251     */
252    // The same as MediaController.PlaybackInfo
253    public static final class PlaybackInfo {
254        /**
255         * The session uses remote playback.
256         */
257        public static final int PLAYBACK_TYPE_REMOTE = 2;
258        /**
259         * The session uses local playback.
260         */
261        public static final int PLAYBACK_TYPE_LOCAL = 1;
262
263        private final PlaybackInfoProvider mProvider;
264
265        /**
266         * @hide
267         */
268        public PlaybackInfo(PlaybackInfoProvider provider) {
269            mProvider = provider;
270        }
271
272        /**
273         * @hide
274         */
275        public PlaybackInfoProvider getProvider() {
276            return mProvider;
277        }
278
279        /**
280         * Get the type of playback which affects volume handling. One of:
281         * <ul>
282         * <li>{@link #PLAYBACK_TYPE_LOCAL}</li>
283         * <li>{@link #PLAYBACK_TYPE_REMOTE}</li>
284         * </ul>
285         *
286         * @return The type of playback this session is using.
287         */
288        public int getPlaybackType() {
289            return mProvider.getPlaybackType_impl();
290        }
291
292        /**
293         * Get the audio attributes for this session. The attributes will affect
294         * volume handling for the session. When the volume type is
295         * {@link PlaybackInfo#PLAYBACK_TYPE_REMOTE} these may be ignored by the
296         * remote volume handler.
297         *
298         * @return The attributes for this session.
299         */
300        public AudioAttributes getAudioAttributes() {
301            return mProvider.getAudioAttributes_impl();
302        }
303
304        /**
305         * Get the type of volume control that can be used. One of:
306         * <ul>
307         * <li>{@link VolumeProvider2#VOLUME_CONTROL_ABSOLUTE}</li>
308         * <li>{@link VolumeProvider2#VOLUME_CONTROL_RELATIVE}</li>
309         * <li>{@link VolumeProvider2#VOLUME_CONTROL_FIXED}</li>
310         * </ul>
311         *
312         * @return The type of volume control that may be used with this session.
313         */
314        public int getControlType() {
315            return mProvider.getControlType_impl();
316        }
317
318        /**
319         * Get the maximum volume that may be set for this session.
320         *
321         * @return The maximum allowed volume where this session is playing.
322         */
323        public int getMaxVolume() {
324            return mProvider.getMaxVolume_impl();
325        }
326
327        /**
328         * Get the current volume for this session.
329         *
330         * @return The current volume where this session is playing.
331         */
332        public int getCurrentVolume() {
333            return mProvider.getCurrentVolume_impl();
334        }
335    }
336
337    private final MediaController2Provider mProvider;
338
339    /**
340     * Create a {@link MediaController2} from the {@link SessionToken2}.
341     * This connects to the session and may wake up the service if it's not available.
342     *
343     * @param context Context
344     * @param token token to connect to
345     * @param executor executor to run callbacks on.
346     * @param callback controller callback to receive changes in
347     */
348    public MediaController2(@NonNull Context context, @NonNull SessionToken2 token,
349            @NonNull @CallbackExecutor Executor executor, @NonNull ControllerCallback callback) {
350        super();
351
352        mProvider = createProvider(context, token, executor, callback);
353        // This also connects to the token.
354        // Explicit connect() isn't added on purpose because retrying connect() is impossible with
355        // session whose session binder is only valid while it's active.
356        // prevent a controller from reusable after the
357        // session is released and recreated.
358        mProvider.initialize();
359    }
360
361    MediaController2Provider createProvider(@NonNull Context context,
362            @NonNull SessionToken2 token, @NonNull Executor executor,
363            @NonNull ControllerCallback callback) {
364        return ApiLoader.getProvider().createMediaController2(
365                context, this, token, executor, callback);
366    }
367
368    /**
369     * Release this object, and disconnect from the session. After this, callbacks wouldn't be
370     * received.
371     */
372    @Override
373    public void close() {
374        mProvider.close_impl();
375    }
376
377    /**
378     * @hide
379     */
380    public MediaController2Provider getProvider() {
381        return mProvider;
382    }
383
384    /**
385     * @return token
386     */
387    public @NonNull SessionToken2 getSessionToken() {
388        return mProvider.getSessionToken_impl();
389    }
390
391    /**
392     * Returns whether this class is connected to active {@link MediaSession2} or not.
393     */
394    public boolean isConnected() {
395        return mProvider.isConnected_impl();
396    }
397
398    public void play() {
399        mProvider.play_impl();
400    }
401
402    public void pause() {
403        mProvider.pause_impl();
404    }
405
406    public void stop() {
407        mProvider.stop_impl();
408    }
409
410    /**
411     * Request that the player prepare its playback. In other words, other sessions can continue
412     * to play during the preparation of this session. This method can be used to speed up the
413     * start of the playback. Once the preparation is done, the session will change its playback
414     * state to {@link MediaPlayerBase#PLAYER_STATE_PAUSED}. Afterwards, {@link #play} can be called
415     * to start playback.
416     */
417    public void prepare() {
418        mProvider.prepare_impl();
419    }
420
421    /**
422     * Fast forwards playback. If playback is already fast forwarding this may increase the rate.
423     */
424    public void fastForward() {
425        mProvider.fastForward_impl();
426    }
427
428    /**
429     * Rewinds playback. If playback is already rewinding this may increase the rate.
430     */
431    public void rewind() {
432        mProvider.rewind_impl();
433    }
434
435    /**
436     * Move to a new location in the media stream.
437     *
438     * @param pos Position to move to, in milliseconds.
439     */
440    public void seekTo(long pos) {
441        mProvider.seekTo_impl(pos);
442    }
443
444    /**
445     * Revisit this API later.
446     * @hide
447     */
448    public void skipForward() {
449        // TODO(jaewan): (Post-P) Discuss this API later.
450        // To match with KEYCODE_MEDIA_SKIP_FORWARD
451    }
452
453    /**
454     * @hide
455     */
456    public void skipBackward() {
457        // TODO(jaewan): (Post-P) Discuss this API later.
458        // To match with KEYCODE_MEDIA_SKIP_BACKWARD
459    }
460
461    /**
462     * Request that the player start playback for a specific media id.
463     *
464     * @param mediaId The id of the requested media.
465     * @param extras Optional extras that can include extra information about the media item
466     *               to be played.
467     */
468    public void playFromMediaId(@NonNull String mediaId, @Nullable Bundle extras) {
469        mProvider.playFromMediaId_impl(mediaId, extras);
470    }
471
472    /**
473     * Request that the player start playback for a specific search query.
474     *
475     * @param query The search query. Should not be an empty string.
476     * @param extras Optional extras that can include extra information about the query.
477     */
478    public void playFromSearch(@NonNull String query, @Nullable Bundle extras) {
479        mProvider.playFromSearch_impl(query, extras);
480    }
481
482    /**
483     * Request that the player start playback for a specific {@link Uri}.
484     *
485     * @param uri The URI of the requested media.
486     * @param extras Optional extras that can include extra information about the media item
487     *               to be played.
488     */
489    public void playFromUri(@NonNull Uri uri, @Nullable Bundle extras) {
490        mProvider.playFromUri_impl(uri, extras);
491    }
492
493    /**
494     * Request that the player prepare playback for a specific media id. In other words, other
495     * sessions can continue to play during the preparation of this session. This method can be
496     * used to speed up the start of the playback. Once the preparation is done, the session
497     * will change its playback state to {@link MediaPlayerBase#PLAYER_STATE_PAUSED}. Afterwards,
498     * {@link #play} can be called to start playback. If the preparation is not needed,
499     * {@link #playFromMediaId} can be directly called without this method.
500     *
501     * @param mediaId The id of the requested media.
502     * @param extras Optional extras that can include extra information about the media item
503     *               to be prepared.
504     */
505    public void prepareFromMediaId(@NonNull String mediaId, @Nullable Bundle extras) {
506        mProvider.prepareFromMediaId_impl(mediaId, extras);
507    }
508
509    /**
510     * Request that the player prepare playback for a specific search query.
511     * In other words, other sessions can continue to play during the preparation of this session.
512     * This method can be used to speed up the start of the playback.
513     * Once the preparation is done, the session will change its playback state to
514     * {@link MediaPlayerBase#PLAYER_STATE_PAUSED}. Afterwards,
515     * {@link #play} can be called to start playback. If the preparation is not needed,
516     * {@link #playFromSearch} can be directly called without this method.
517     *
518     * @param query The search query. Should not be an empty string.
519     * @param extras Optional extras that can include extra information about the query.
520     */
521    public void prepareFromSearch(@NonNull String query, @Nullable Bundle extras) {
522        mProvider.prepareFromSearch_impl(query, extras);
523    }
524
525    /**
526     * Request that the player prepare playback for a specific {@link Uri}. In other words,
527     * other sessions can continue to play during the preparation of this session. This method
528     * can be used to speed up the start of the playback. Once the preparation is done, the
529     * session will change its playback state to {@link MediaPlayerBase#PLAYER_STATE_PAUSED}.
530     * Afterwards, {@link #play} can be called to start playback. If the preparation is not needed,
531     * {@link #playFromUri} can be directly called without this method.
532     *
533     * @param uri The URI of the requested media.
534     * @param extras Optional extras that can include extra information about the media item
535     *               to be prepared.
536     */
537    public void prepareFromUri(@NonNull Uri uri, @Nullable Bundle extras) {
538        mProvider.prepareFromUri_impl(uri, extras);
539    }
540
541    /**
542     * Set the volume of the output this session is playing on. The command will be ignored if it
543     * does not support {@link VolumeProvider2#VOLUME_CONTROL_ABSOLUTE}.
544     * <p>
545     * If the session is local playback, this changes the device's volume with the stream that
546     * session's player is using. Flags will be specified for the {@link AudioManager}.
547     * <p>
548     * If the session is remote player (i.e. session has set volume provider), its volume provider
549     * will receive this request instead.
550     *
551     * @see #getPlaybackInfo()
552     * @param value The value to set it to, between 0 and the reported max.
553     * @param flags flags from {@link AudioManager} to include with the volume request for local
554     *              playback
555     */
556    public void setVolumeTo(int value, int flags) {
557        mProvider.setVolumeTo_impl(value, flags);
558    }
559
560    /**
561     * Adjust the volume of the output this session is playing on. The direction
562     * must be one of {@link AudioManager#ADJUST_LOWER},
563     * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}.
564     * The command will be ignored if the session does not support
565     * {@link VolumeProvider2#VOLUME_CONTROL_RELATIVE} or
566     * {@link VolumeProvider2#VOLUME_CONTROL_ABSOLUTE}.
567     * <p>
568     * If the session is local playback, this changes the device's volume with the stream that
569     * session's player is using. Flags will be specified for the {@link AudioManager}.
570     * <p>
571     * If the session is remote player (i.e. session has set volume provider), its volume provider
572     * will receive this request instead.
573     *
574     * @see #getPlaybackInfo()
575     * @param direction The direction to adjust the volume in.
576     * @param flags flags from {@link AudioManager} to include with the volume request for local
577     *              playback
578     */
579    public void adjustVolume(int direction, int flags) {
580        mProvider.adjustVolume_impl(direction, flags);
581    }
582
583    /**
584     * Get an intent for launching UI associated with this session if one exists.
585     *
586     * @return A {@link PendingIntent} to launch UI or null.
587     */
588    public @Nullable PendingIntent getSessionActivity() {
589        return mProvider.getSessionActivity_impl();
590    }
591
592    /**
593     * Get the lastly cached player state from
594     * {@link ControllerCallback#onPlayerStateChanged(MediaController2, int)}.
595     *
596     * @return player state
597     */
598    public int getPlayerState() {
599        return mProvider.getPlayerState_impl();
600    }
601
602    /**
603     * Gets the current playback position.
604     * <p>
605     * This returns the calculated value of the position, based on the difference between the
606     * update time and current time.
607     *
608     * @return position
609     */
610    public long getCurrentPosition() {
611        return mProvider.getCurrentPosition_impl();
612    }
613
614    /**
615     * Get the lastly cached playback speed from
616     * {@link ControllerCallback#onPlaybackSpeedChanged(MediaController2, float)}.
617     *
618     * @return speed
619     */
620    public float getPlaybackSpeed() {
621        return mProvider.getPlaybackSpeed_impl();
622    }
623
624    /**
625     * Set the playback speed.
626     */
627    public void setPlaybackSpeed(float speed) {
628        // TODO(jaewan): implement this (b/74093080)
629    }
630
631
632    /**
633     * Gets the current buffering state of the player.
634     * During buffering, see {@link #getBufferedPosition()} for the quantifying the amount already
635     * buffered.
636     * @return the buffering state.
637     */
638    public @MediaPlayerBase.BuffState int getBufferingState() {
639        // TODO(jaewan): Implement.
640        return BUFFERING_STATE_UNKNOWN;
641    }
642
643    /**
644     * Gets the lastly cached buffered position from the session when
645     * {@link ControllerCallback#onBufferingStateChanged(MediaController2, MediaItem2, int)} is
646     * called.
647     *
648     * @return buffering position in millis
649     */
650    public long getBufferedPosition() {
651        return mProvider.getBufferedPosition_impl();
652    }
653
654    /**
655     * Get the current playback info for this session.
656     *
657     * @return The current playback info or null.
658     */
659    public @Nullable PlaybackInfo getPlaybackInfo() {
660        return mProvider.getPlaybackInfo_impl();
661    }
662
663    /**
664     * Rate the media. This will cause the rating to be set for the current user.
665     * The rating style must follow the user rating style from the session.
666     * You can get the rating style from the session through the
667     * {@link MediaMetadata#getRating(String)} with the key
668     * {@link MediaMetadata#METADATA_KEY_USER_RATING}.
669     * <p>
670     * If the user rating was {@code null}, the media item does not accept setting user rating.
671     *
672     * @param mediaId The id of the media
673     * @param rating The rating to set
674     */
675    public void setRating(@NonNull String mediaId, @NonNull Rating2 rating) {
676        mProvider.setRating_impl(mediaId, rating);
677    }
678
679    /**
680     * Send custom command to the session
681     *
682     * @param command custom command
683     * @param args optional argument
684     * @param cb optional result receiver
685     */
686    public void sendCustomCommand(@NonNull SessionCommand2 command, @Nullable Bundle args,
687            @Nullable ResultReceiver cb) {
688        mProvider.sendCustomCommand_impl(command, args, cb);
689    }
690
691    /**
692     * Returns the cached playlist from
693     * {@link ControllerCallback#onPlaylistChanged(MediaController2, List, MediaMetadata2)}.
694     * <p>
695     * This list may differ with the list that was specified with
696     * {@link #setPlaylist(List, MediaMetadata2)} depending on the session implementation. Use media
697     * items returned here for other playlist APIs such as {@link #skipToPlaylistItem(MediaItem2)}.
698     *
699     * @return The playlist. Can be {@code null} if the controller doesn't have enough permission or
700     *         the session hasn't set any playlist.
701     */
702    public @Nullable List<MediaItem2> getPlaylist() {
703        return mProvider.getPlaylist_impl();
704    }
705
706    /**
707     * Sets the playlist.
708     * <p>
709     * Even when the playlist is successfully set, use the playlist returned from
710     * {@link #getPlaylist()} for playlist APIs such as {@link #skipToPlaylistItem(MediaItem2)}.
711     * Otherwise the session in the remote process can't distinguish between media items.
712     *
713     * @param list playlist
714     * @param metadata metadata of the playlist
715     * @see #getPlaylist()
716     * @see ControllerCallback#onPlaylistChanged(MediaController2, List, MediaMetadata2)
717     */
718    public void setPlaylist(@NonNull List<MediaItem2> list, @Nullable MediaMetadata2 metadata) {
719        mProvider.setPlaylist_impl(list, metadata);
720    }
721
722    /**
723     * Updates the playlist metadata
724     *
725     * @param metadata metadata of the playlist
726     */
727    public void updatePlaylistMetadata(@Nullable MediaMetadata2 metadata) {
728        mProvider.updatePlaylistMetadata_impl(metadata);
729    }
730
731    /**
732     * Gets the lastly cached playlist playlist metadata either from
733     * {@link ControllerCallback#onPlaylistMetadataChanged(MediaController2,  MediaMetadata2)} or
734     * {@link ControllerCallback#onPlaylistChanged(MediaController2, List, MediaMetadata2)}.
735     *
736     * @return metadata metadata of the playlist, or null if none is set
737     */
738    public @Nullable MediaMetadata2 getPlaylistMetadata() {
739        return mProvider.getPlaylistMetadata_impl();
740    }
741
742
743    /**
744     * Adds the media item to the playlist at position index. Index equals or greater than
745     * the current playlist size will add the item at the end of the playlist.
746     * <p>
747     * This will not change the currently playing media item.
748     * If index is less than or equal to the current index of the playlist,
749     * the current index of the playlist will be incremented correspondingly.
750     *
751     * @param index the index you want to add
752     * @param item the media item you want to add
753     */
754    public void addPlaylistItem(int index, @NonNull MediaItem2 item) {
755        mProvider.addPlaylistItem_impl(index, item);
756    }
757
758    /**
759     * Removes the media item at index in the playlist.
760     *<p>
761     * If the item is the currently playing item of the playlist, current playback
762     * will be stopped and playback moves to next source in the list.
763     *
764     * @param item the media item you want to add
765     */
766    public void removePlaylistItem(@NonNull MediaItem2 item) {
767        mProvider.removePlaylistItem_impl(item);
768    }
769
770    /**
771     * Replace the media item at index in the playlist. This can be also used to update metadata of
772     * an item.
773     *
774     * @param index the index of the item to replace
775     * @param item the new item
776     */
777    public void replacePlaylistItem(int index, @NonNull MediaItem2 item) {
778        mProvider.replacePlaylistItem_impl(index, item);
779    }
780
781    /**
782     * Get the lastly cached current item from
783     * {@link ControllerCallback#onCurrentMediaItemChanged(MediaController2, MediaItem2)}.
784     *
785     * @return index of the current item
786     */
787    public MediaItem2 getCurrentMediaItem() {
788        return mProvider.getCurrentMediaItem_impl();
789    }
790
791    /**
792     * Skips to the previous item in the playlist.
793     * <p>
794     * This calls {@link MediaSession2#skipToPreviousItem()} if the session allows.
795     */
796     public void skipToPreviousItem() {
797         mProvider.skipToPreviousItem_impl();
798     }
799
800    /**
801     * Skips to the next item in the playlist.
802     * <p>
803     * This calls {@link MediaSession2#skipToNextItem()} if the session allows.
804     */
805    public void skipToNextItem() {
806        mProvider.skipToNextItem_impl();
807    }
808
809    /**
810     * Skips to the item in the playlist.
811     * <p>
812     * This calls {@link MediaSession2#skipToPlaylistItem(MediaItem2)} if the session allows.
813     *
814     * @param item The item in the playlist you want to play
815     */
816    public void skipToPlaylistItem(@NonNull MediaItem2 item) {
817        mProvider.skipToPlaylistItem_impl(item);
818    }
819
820    /**
821     * Gets the cached repeat mode from the {@link ControllerCallback#onRepeatModeChanged(
822     * MediaController2, int)}.
823     *
824     * @return repeat mode
825     * @see MediaPlaylistAgent#REPEAT_MODE_NONE
826     * @see MediaPlaylistAgent#REPEAT_MODE_ONE
827     * @see MediaPlaylistAgent#REPEAT_MODE_ALL
828     * @see MediaPlaylistAgent#REPEAT_MODE_GROUP
829     */
830    public @RepeatMode int getRepeatMode() {
831        return mProvider.getRepeatMode_impl();
832    }
833
834    /**
835     * Sets the repeat mode.
836     *
837     * @param repeatMode repeat mode
838     * @see MediaPlaylistAgent#REPEAT_MODE_NONE
839     * @see MediaPlaylistAgent#REPEAT_MODE_ONE
840     * @see MediaPlaylistAgent#REPEAT_MODE_ALL
841     * @see MediaPlaylistAgent#REPEAT_MODE_GROUP
842     */
843    public void setRepeatMode(@RepeatMode int repeatMode) {
844        mProvider.setRepeatMode_impl(repeatMode);
845    }
846
847    /**
848     * Gets the cached shuffle mode from the {@link ControllerCallback#onShuffleModeChanged(
849     * MediaController2, int)}.
850     *
851     * @return The shuffle mode
852     * @see MediaPlaylistAgent#SHUFFLE_MODE_NONE
853     * @see MediaPlaylistAgent#SHUFFLE_MODE_ALL
854     * @see MediaPlaylistAgent#SHUFFLE_MODE_GROUP
855     */
856    public @ShuffleMode int getShuffleMode() {
857        return mProvider.getShuffleMode_impl();
858    }
859
860    /**
861     * Sets the shuffle mode.
862     *
863     * @param shuffleMode The shuffle mode
864     * @see MediaPlaylistAgent#SHUFFLE_MODE_NONE
865     * @see MediaPlaylistAgent#SHUFFLE_MODE_ALL
866     * @see MediaPlaylistAgent#SHUFFLE_MODE_GROUP
867     */
868    public void setShuffleMode(@ShuffleMode int shuffleMode) {
869        mProvider.setShuffleMode_impl(shuffleMode);
870    }
871}
872