Home | History | Annotate | Download | only in media
      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 
     17 package android.media;
     18 
     19 import static android.media.MediaPlayerBase.BUFFERING_STATE_UNKNOWN;
     20 
     21 import android.annotation.CallbackExecutor;
     22 import android.annotation.IntDef;
     23 import android.annotation.NonNull;
     24 import android.annotation.Nullable;
     25 import android.app.PendingIntent;
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.media.MediaPlayerBase.BuffState;
     29 import android.media.MediaPlayerBase.PlayerState;
     30 import android.media.MediaPlaylistAgent.RepeatMode;
     31 import android.media.MediaPlaylistAgent.ShuffleMode;
     32 import android.media.update.ApiLoader;
     33 import android.media.update.MediaSession2Provider;
     34 import android.media.update.MediaSession2Provider.BuilderBaseProvider;
     35 import android.media.update.MediaSession2Provider.CommandButtonProvider;
     36 import android.media.update.MediaSession2Provider.ControllerInfoProvider;
     37 import android.media.update.ProviderCreator;
     38 import android.net.Uri;
     39 import android.os.Bundle;
     40 import android.os.IInterface;
     41 import android.os.ResultReceiver;
     42 
     43 import java.lang.annotation.Retention;
     44 import java.lang.annotation.RetentionPolicy;
     45 import java.util.List;
     46 import java.util.concurrent.Executor;
     47 
     48 /**
     49  * @hide
     50  * Allows a media app to expose its transport controls and playback information in a process to
     51  * other processes including the Android framework and other apps. Common use cases are as follows.
     52  * <ul>
     53  *     <li>Bluetooth/wired headset key events support</li>
     54  *     <li>Android Auto/Wearable support</li>
     55  *     <li>Separating UI process and playback process</li>
     56  * </ul>
     57  * <p>
     58  * A MediaSession2 should be created when an app wants to publish media playback information or
     59  * handle media keys. In general an app only needs one session for all playback, though multiple
     60  * sessions can be created to provide finer grain controls of media.
     61  * <p>
     62  * If you want to support background playback, {@link MediaSessionService2} is preferred
     63  * instead. With it, your playback can be revived even after playback is finished. See
     64  * {@link MediaSessionService2} for details.
     65  * <p>
     66  * A session can be obtained by {@link Builder}. The owner of the session may pass its session token
     67  * to other processes to allow them to create a {@link MediaController2} to interact with the
     68  * session.
     69  * <p>
     70  * When a session receive transport control commands, the session sends the commands directly to
     71  * the the underlying media player set by {@link Builder} or
     72  * {@link #updatePlayer}.
     73  * <p>
     74  * When an app is finished performing playback it must call {@link #close()} to clean up the session
     75  * and notify any controllers.
     76  * <p>
     77  * {@link MediaSession2} objects should be used on the thread on the looper.
     78  *
     79  * @see MediaSessionService2
     80  */
     81 public class MediaSession2 implements AutoCloseable {
     82     private final MediaSession2Provider mProvider;
     83 
     84     /**
     85      * @hide
     86      */
     87     @IntDef({ERROR_CODE_UNKNOWN_ERROR, ERROR_CODE_APP_ERROR, ERROR_CODE_NOT_SUPPORTED,
     88             ERROR_CODE_AUTHENTICATION_EXPIRED, ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED,
     89             ERROR_CODE_CONCURRENT_STREAM_LIMIT, ERROR_CODE_PARENTAL_CONTROL_RESTRICTED,
     90             ERROR_CODE_NOT_AVAILABLE_IN_REGION, ERROR_CODE_CONTENT_ALREADY_PLAYING,
     91             ERROR_CODE_SKIP_LIMIT_REACHED, ERROR_CODE_ACTION_ABORTED, ERROR_CODE_END_OF_QUEUE,
     92             ERROR_CODE_SETUP_REQUIRED})
     93     @Retention(RetentionPolicy.SOURCE)
     94     public @interface ErrorCode {}
     95 
     96     /**
     97      * This is the default error code and indicates that none of the other error codes applies.
     98      */
     99     public static final int ERROR_CODE_UNKNOWN_ERROR = 0;
    100 
    101     /**
    102      * Error code when the application state is invalid to fulfill the request.
    103      */
    104     public static final int ERROR_CODE_APP_ERROR = 1;
    105 
    106     /**
    107      * Error code when the request is not supported by the application.
    108      */
    109     public static final int ERROR_CODE_NOT_SUPPORTED = 2;
    110 
    111     /**
    112      * Error code when the request cannot be performed because authentication has expired.
    113      */
    114     public static final int ERROR_CODE_AUTHENTICATION_EXPIRED = 3;
    115 
    116     /**
    117      * Error code when a premium account is required for the request to succeed.
    118      */
    119     public static final int ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED = 4;
    120 
    121     /**
    122      * Error code when too many concurrent streams are detected.
    123      */
    124     public static final int ERROR_CODE_CONCURRENT_STREAM_LIMIT = 5;
    125 
    126     /**
    127      * Error code when the content is blocked due to parental controls.
    128      */
    129     public static final int ERROR_CODE_PARENTAL_CONTROL_RESTRICTED = 6;
    130 
    131     /**
    132      * Error code when the content is blocked due to being regionally unavailable.
    133      */
    134     public static final int ERROR_CODE_NOT_AVAILABLE_IN_REGION = 7;
    135 
    136     /**
    137      * Error code when the requested content is already playing.
    138      */
    139     public static final int ERROR_CODE_CONTENT_ALREADY_PLAYING = 8;
    140 
    141     /**
    142      * Error code when the application cannot skip any more songs because skip limit is reached.
    143      */
    144     public static final int ERROR_CODE_SKIP_LIMIT_REACHED = 9;
    145 
    146     /**
    147      * Error code when the action is interrupted due to some external event.
    148      */
    149     public static final int ERROR_CODE_ACTION_ABORTED = 10;
    150 
    151     /**
    152      * Error code when the playback navigation (previous, next) is not possible because the queue
    153      * was exhausted.
    154      */
    155     public static final int ERROR_CODE_END_OF_QUEUE = 11;
    156 
    157     /**
    158      * Error code when the session needs user's manual intervention.
    159      */
    160     public static final int ERROR_CODE_SETUP_REQUIRED = 12;
    161 
    162     /**
    163      * Interface definition of a callback to be invoked when a {@link MediaItem2} in the playlist
    164      * didn't have a {@link DataSourceDesc} but it's needed now for preparing or playing it.
    165      *
    166      * #see #setOnDataSourceMissingHelper
    167      */
    168     public interface OnDataSourceMissingHelper {
    169         /**
    170          * Called when a {@link MediaItem2} in the playlist didn't have a {@link DataSourceDesc}
    171          * but it's needed now for preparing or playing it. Returned data source descriptor will be
    172          * sent to the player directly to prepare or play the contents.
    173          * <p>
    174          * An exception may be thrown if the returned {@link DataSourceDesc} is duplicated in the
    175          * playlist, so items cannot be differentiated.
    176          *
    177          * @param session the session for this event
    178          * @param item media item from the controller
    179          * @return a data source descriptor if the media item. Can be {@code null} if the content
    180          *        isn't available.
    181          */
    182         @Nullable DataSourceDesc onDataSourceMissing(@NonNull MediaSession2 session,
    183                 @NonNull MediaItem2 item);
    184     }
    185 
    186     /**
    187      * Callback to be called for all incoming commands from {@link MediaController2}s.
    188      * <p>
    189      * If it's not set, the session will accept all controllers and all incoming commands by
    190      * default.
    191      */
    192     // TODO(jaewan): Move this to updatable for default implementation (b/74091963)
    193     public static abstract class SessionCallback {
    194         /**
    195          * Called when a controller is created for this session. Return allowed commands for
    196          * controller. By default it allows all connection requests and commands.
    197          * <p>
    198          * You can reject the connection by return {@code null}. In that case, controller receives
    199          * {@link MediaController2.ControllerCallback#onDisconnected(MediaController2)} and cannot
    200          * be usable.
    201          *
    202          * @param session the session for this event
    203          * @param controller controller information.
    204          * @return allowed commands. Can be {@code null} to reject connection.
    205          */
    206         public @Nullable SessionCommandGroup2 onConnect(@NonNull MediaSession2 session,
    207                 @NonNull ControllerInfo controller) {
    208             SessionCommandGroup2 commands = new SessionCommandGroup2();
    209             commands.addAllPredefinedCommands();
    210             return commands;
    211         }
    212 
    213         /**
    214          * Called when a controller is disconnected
    215          *
    216          * @param session the session for this event
    217          * @param controller controller information
    218          */
    219         public void onDisconnected(@NonNull MediaSession2 session,
    220                 @NonNull ControllerInfo controller) { }
    221 
    222         /**
    223          * Called when a controller sent a command that will be sent directly to the player. Return
    224          * {@code false} here to reject the request and stop sending command to the player.
    225          *
    226          * @param session the session for this event
    227          * @param controller controller information.
    228          * @param command a command. This method will be called for every single command.
    229          * @return {@code true} if you want to accept incoming command. {@code false} otherwise.
    230          * @see SessionCommand2#COMMAND_CODE_PLAYBACK_PLAY
    231          * @see SessionCommand2#COMMAND_CODE_PLAYBACK_PAUSE
    232          * @see SessionCommand2#COMMAND_CODE_PLAYBACK_STOP
    233          * @see SessionCommand2#COMMAND_CODE_PLAYLIST_SKIP_NEXT_ITEM
    234          * @see SessionCommand2#COMMAND_CODE_PLAYLIST_SKIP_PREV_ITEM
    235          * @see SessionCommand2#COMMAND_CODE_PLAYBACK_PREPARE
    236          * @see SessionCommand2#COMMAND_CODE_SESSION_FAST_FORWARD
    237          * @see SessionCommand2#COMMAND_CODE_SESSION_REWIND
    238          * @see SessionCommand2#COMMAND_CODE_PLAYBACK_SEEK_TO
    239          * @see SessionCommand2#COMMAND_CODE_PLAYLIST_SKIP_TO_PLAYLIST_ITEM
    240          * @see SessionCommand2#COMMAND_CODE_PLAYLIST_ADD_ITEM
    241          * @see SessionCommand2#COMMAND_CODE_PLAYLIST_REMOVE_ITEM
    242          * @see SessionCommand2#COMMAND_CODE_PLAYLIST_GET_LIST
    243          * @see SessionCommand2#COMMAND_CODE_SET_VOLUME
    244          */
    245         public boolean onCommandRequest(@NonNull MediaSession2 session,
    246                 @NonNull ControllerInfo controller, @NonNull SessionCommand2 command) {
    247             return true;
    248         }
    249 
    250         /**
    251          * Called when a controller set rating of a media item through
    252          * {@link MediaController2#setRating(String, Rating2)}.
    253          * <p>
    254          * To allow setting user rating for a {@link MediaItem2}, the media item's metadata
    255          * should have {@link Rating2} with the key {@link MediaMetadata#METADATA_KEY_USER_RATING},
    256          * in order to provide possible rating style for controller. Controller will follow the
    257          * rating style.
    258          *
    259          * @param session the session for this event
    260          * @param controller controller information
    261          * @param mediaId media id from the controller
    262          * @param rating new rating from the controller
    263          */
    264         public void onSetRating(@NonNull MediaSession2 session, @NonNull ControllerInfo controller,
    265                 @NonNull String mediaId, @NonNull Rating2 rating) { }
    266 
    267         /**
    268          * Called when a controller sent a custom command through
    269          * {@link MediaController2#sendCustomCommand(SessionCommand2, Bundle, ResultReceiver)}.
    270          *
    271          * @param session the session for this event
    272          * @param controller controller information
    273          * @param customCommand custom command.
    274          * @param args optional arguments
    275          * @param cb optional result receiver
    276          */
    277         public void onCustomCommand(@NonNull MediaSession2 session,
    278                 @NonNull ControllerInfo controller, @NonNull SessionCommand2 customCommand,
    279                 @Nullable Bundle args, @Nullable ResultReceiver cb) { }
    280 
    281         /**
    282          * Called when a controller requested to play a specific mediaId through
    283          * {@link MediaController2#playFromMediaId(String, Bundle)}.
    284          *
    285          * @param session the session for this event
    286          * @param controller controller information
    287          * @param mediaId media id
    288          * @param extras optional extra bundle
    289          * @see SessionCommand2#COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID
    290          */
    291         public void onPlayFromMediaId(@NonNull MediaSession2 session,
    292                 @NonNull ControllerInfo controller, @NonNull String mediaId,
    293                 @Nullable Bundle extras) { }
    294 
    295         /**
    296          * Called when a controller requested to begin playback from a search query through
    297          * {@link MediaController2#playFromSearch(String, Bundle)}
    298          * <p>
    299          * An empty query indicates that the app may play any music. The implementation should
    300          * attempt to make a smart choice about what to play.
    301          *
    302          * @param session the session for this event
    303          * @param controller controller information
    304          * @param query query string. Can be empty to indicate any suggested media
    305          * @param extras optional extra bundle
    306          * @see SessionCommand2#COMMAND_CODE_SESSION_PLAY_FROM_SEARCH
    307          */
    308         public void onPlayFromSearch(@NonNull MediaSession2 session,
    309                 @NonNull ControllerInfo controller, @NonNull String query,
    310                 @Nullable Bundle extras) { }
    311 
    312         /**
    313          * Called when a controller requested to play a specific media item represented by a URI
    314          * through {@link MediaController2#playFromUri(Uri, Bundle)}
    315          *
    316          * @param session the session for this event
    317          * @param controller controller information
    318          * @param uri uri
    319          * @param extras optional extra bundle
    320          * @see SessionCommand2#COMMAND_CODE_SESSION_PLAY_FROM_URI
    321          */
    322         public void onPlayFromUri(@NonNull MediaSession2 session,
    323                 @NonNull ControllerInfo controller, @NonNull Uri uri,
    324                 @Nullable Bundle extras) { }
    325 
    326         /**
    327          * Called when a controller requested to prepare for playing a specific mediaId through
    328          * {@link MediaController2#prepareFromMediaId(String, Bundle)}.
    329          * <p>
    330          * During the preparation, a session should not hold audio focus in order to allow other
    331          * sessions play seamlessly. The state of playback should be updated to
    332          * {@link MediaPlayerBase#PLAYER_STATE_PAUSED} after the preparation is done.
    333          * <p>
    334          * The playback of the prepared content should start in the later calls of
    335          * {@link MediaSession2#play()}.
    336          * <p>
    337          * Override {@link #onPlayFromMediaId} to handle requests for starting
    338          * playback without preparation.
    339          *
    340          * @param session the session for this event
    341          * @param controller controller information
    342          * @param mediaId media id to prepare
    343          * @param extras optional extra bundle
    344          * @see SessionCommand2#COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID
    345          */
    346         public void onPrepareFromMediaId(@NonNull MediaSession2 session,
    347                 @NonNull ControllerInfo controller, @NonNull String mediaId,
    348                 @Nullable Bundle extras) { }
    349 
    350         /**
    351          * Called when a controller requested to prepare playback from a search query through
    352          * {@link MediaController2#prepareFromSearch(String, Bundle)}.
    353          * <p>
    354          * An empty query indicates that the app may prepare any music. The implementation should
    355          * attempt to make a smart choice about what to play.
    356          * <p>
    357          * The state of playback should be updated to {@link MediaPlayerBase#PLAYER_STATE_PAUSED}
    358          * after the preparation is done. The playback of the prepared content should start in the
    359          * later calls of {@link MediaSession2#play()}.
    360          * <p>
    361          * Override {@link #onPlayFromSearch} to handle requests for starting playback without
    362          * preparation.
    363          *
    364          * @param session the session for this event
    365          * @param controller controller information
    366          * @param query query string. Can be empty to indicate any suggested media
    367          * @param extras optional extra bundle
    368          * @see SessionCommand2#COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH
    369          */
    370         public void onPrepareFromSearch(@NonNull MediaSession2 session,
    371                 @NonNull ControllerInfo controller, @NonNull String query,
    372                 @Nullable Bundle extras) { }
    373 
    374         /**
    375          * Called when a controller requested to prepare a specific media item represented by a URI
    376          * through {@link MediaController2#prepareFromUri(Uri, Bundle)}.
    377          * <p>
    378          * During the preparation, a session should not hold audio focus in order to allow
    379          * other sessions play seamlessly. The state of playback should be updated to
    380          * {@link MediaPlayerBase#PLAYER_STATE_PAUSED} after the preparation is done.
    381          * <p>
    382          * The playback of the prepared content should start in the later calls of
    383          * {@link MediaSession2#play()}.
    384          * <p>
    385          * Override {@link #onPlayFromUri} to handle requests for starting playback without
    386          * preparation.
    387          *
    388          * @param session the session for this event
    389          * @param controller controller information
    390          * @param uri uri
    391          * @param extras optional extra bundle
    392          * @see SessionCommand2#COMMAND_CODE_SESSION_PREPARE_FROM_URI
    393          */
    394         public void onPrepareFromUri(@NonNull MediaSession2 session,
    395                 @NonNull ControllerInfo controller, @NonNull Uri uri, @Nullable Bundle extras) { }
    396 
    397         /**
    398          * Called when a controller called {@link MediaController2#fastForward()}
    399          *
    400          * @param session the session for this event
    401          */
    402         public void onFastForward(@NonNull MediaSession2 session) { }
    403 
    404         /**
    405          * Called when a controller called {@link MediaController2#rewind()}
    406          *
    407          * @param session the session for this event
    408          */
    409         public void onRewind(@NonNull MediaSession2 session) { }
    410 
    411         /**
    412          * Called when the player's current playing item is changed
    413          * <p>
    414          * When it's called, you should invalidate previous playback information and wait for later
    415          * callbacks.
    416          *
    417          * @param session the controller for this event
    418          * @param player the player for this event
    419          * @param item new item
    420          */
    421         // TODO(jaewan): Use this (b/74316764)
    422         public void onCurrentMediaItemChanged(@NonNull MediaSession2 session,
    423                 @NonNull MediaPlayerBase player, @NonNull MediaItem2 item) { }
    424 
    425         /**
    426          * Called when the player is <i>prepared</i>, i.e. it is ready to play the content
    427          * referenced by the given data source.
    428          * @param session the session for this event
    429          * @param player the player for this event
    430          * @param item the media item for which buffering is happening
    431          */
    432         public void onMediaPrepared(@NonNull MediaSession2 session, @NonNull MediaPlayerBase player,
    433                 @NonNull MediaItem2 item) { }
    434 
    435         /**
    436          * Called to indicate that the state of the player has changed.
    437          * See {@link MediaPlayerBase#getPlayerState()} for polling the player state.
    438          * @param session the session for this event
    439          * @param player the player for this event
    440          * @param state the new state of the player.
    441          */
    442         public void onPlayerStateChanged(@NonNull MediaSession2 session,
    443                 @NonNull MediaPlayerBase player, @PlayerState int state) { }
    444 
    445         /**
    446          * Called to report buffering events for a data source.
    447          *
    448          * @param session the session for this event
    449          * @param player the player for this event
    450          * @param item the media item for which buffering is happening.
    451          * @param state the new buffering state.
    452          */
    453         public void onBufferingStateChanged(@NonNull MediaSession2 session,
    454                 @NonNull MediaPlayerBase player, @NonNull MediaItem2 item, @BuffState int state) { }
    455 
    456         /**
    457          * Called to indicate that the playback speed has changed.
    458          * @param session the session for this event
    459          * @param player the player for this event
    460          * @param speed the new playback speed.
    461          */
    462         public void onPlaybackSpeedChanged(@NonNull MediaSession2 session,
    463                 @NonNull MediaPlayerBase player, float speed) { }
    464 
    465         /**
    466          * Called to indicate that {@link #seekTo(long)} is completed.
    467          *
    468          * @param session the session for this event.
    469          * @param mpb the player that has completed seeking.
    470          * @param position the previous seeking request.
    471          * @see #seekTo(long)
    472          */
    473         public void onSeekCompleted(@NonNull MediaSession2 session, @NonNull MediaPlayerBase mpb,
    474                 long position) { }
    475 
    476         /**
    477          * Called when a playlist is changed from the {@link MediaPlaylistAgent}.
    478          * <p>
    479          * This is called when the underlying agent has called
    480          * {@link MediaPlaylistAgent.PlaylistEventCallback#onPlaylistChanged(MediaPlaylistAgent,
    481          * List, MediaMetadata2)}.
    482          *
    483          * @param session the session for this event
    484          * @param playlistAgent playlist agent for this event
    485          * @param list new playlist
    486          * @param metadata new metadata
    487          */
    488         public void onPlaylistChanged(@NonNull MediaSession2 session,
    489                 @NonNull MediaPlaylistAgent playlistAgent, @NonNull List<MediaItem2> list,
    490                 @Nullable MediaMetadata2 metadata) { }
    491 
    492         /**
    493          * Called when a playlist metadata is changed.
    494          *
    495          * @param session the session for this event
    496          * @param playlistAgent playlist agent for this event
    497          * @param metadata new metadata
    498          */
    499         public void onPlaylistMetadataChanged(@NonNull MediaSession2 session,
    500                 @NonNull MediaPlaylistAgent playlistAgent, @Nullable MediaMetadata2 metadata) { }
    501 
    502         /**
    503          * Called when the shuffle mode is changed.
    504          *
    505          * @param session the session for this event
    506          * @param playlistAgent playlist agent for this event
    507          * @param shuffleMode repeat mode
    508          * @see MediaPlaylistAgent#SHUFFLE_MODE_NONE
    509          * @see MediaPlaylistAgent#SHUFFLE_MODE_ALL
    510          * @see MediaPlaylistAgent#SHUFFLE_MODE_GROUP
    511          */
    512         public void onShuffleModeChanged(@NonNull MediaSession2 session,
    513                 @NonNull MediaPlaylistAgent playlistAgent,
    514                 @MediaPlaylistAgent.ShuffleMode int shuffleMode) { }
    515 
    516         /**
    517          * Called when the repeat mode is changed.
    518          *
    519          * @param session the session for this event
    520          * @param playlistAgent playlist agent for this event
    521          * @param repeatMode repeat mode
    522          * @see MediaPlaylistAgent#REPEAT_MODE_NONE
    523          * @see MediaPlaylistAgent#REPEAT_MODE_ONE
    524          * @see MediaPlaylistAgent#REPEAT_MODE_ALL
    525          * @see MediaPlaylistAgent#REPEAT_MODE_GROUP
    526          */
    527         public void onRepeatModeChanged(@NonNull MediaSession2 session,
    528                 @NonNull MediaPlaylistAgent playlistAgent,
    529                 @MediaPlaylistAgent.RepeatMode int repeatMode) { }
    530     }
    531 
    532     /**
    533      * Base builder class for MediaSession2 and its subclass. Any change in this class should be
    534      * also applied to the subclasses {@link MediaSession2.Builder} and
    535      * {@link MediaLibraryService2.MediaLibrarySession.Builder}.
    536      * <p>
    537      * APIs here should be package private, but should have documentations for developers.
    538      * Otherwise, javadoc will generate documentation with the generic types such as follows.
    539      * <pre>U extends BuilderBase<T, U, C> setSessionCallback(Executor executor, C callback)</pre>
    540      * <p>
    541      * This class is hidden to prevent from generating test stub, which fails with
    542      * 'unexpected bound' because it tries to auto generate stub class as follows.
    543      * <pre>abstract static class BuilderBase<
    544      *      T extends android.media.MediaSession2,
    545      *      U extends android.media.MediaSession2.BuilderBase<
    546      *              T, U, C extends android.media.MediaSession2.SessionCallback>, C></pre>
    547      * @hide
    548      */
    549     static abstract class BuilderBase
    550             <T extends MediaSession2, U extends BuilderBase<T, U, C>, C extends SessionCallback> {
    551         private final BuilderBaseProvider<T, C> mProvider;
    552 
    553         BuilderBase(ProviderCreator<BuilderBase<T, U, C>, BuilderBaseProvider<T, C>> creator) {
    554             mProvider = creator.createProvider(this);
    555         }
    556 
    557         /**
    558          * Sets the underlying {@link MediaPlayerBase} for this session to dispatch incoming event
    559          * to.
    560          *
    561          * @param player a {@link MediaPlayerBase} that handles actual media playback in your app.
    562          */
    563         @NonNull U setPlayer(@NonNull MediaPlayerBase player) {
    564             mProvider.setPlayer_impl(player);
    565             return (U) this;
    566         }
    567 
    568         /**
    569          * Sets the {@link MediaPlaylistAgent} for this session to manages playlist of the
    570          * underlying {@link MediaPlayerBase}. The playlist agent should manage
    571          * {@link MediaPlayerBase} for calling {@link MediaPlayerBase#setNextDataSources(List)}.
    572          * <p>
    573          * If the {@link MediaPlaylistAgent} isn't set, session will create the default playlist
    574          * agent.
    575          *
    576          * @param playlistAgent a {@link MediaPlaylistAgent} that manages playlist of the
    577          *                      {@code player}
    578          */
    579         U setPlaylistAgent(@NonNull MediaPlaylistAgent playlistAgent) {
    580             mProvider.setPlaylistAgent_impl(playlistAgent);
    581             return (U) this;
    582         }
    583 
    584         /**
    585          * Sets the {@link VolumeProvider2} for this session to handle volume events. If not set,
    586          * system will adjust the appropriate stream volume for this session's player.
    587          *
    588          * @param volumeProvider The provider that will receive volume button events.
    589          */
    590         @NonNull U setVolumeProvider(@Nullable VolumeProvider2 volumeProvider) {
    591             mProvider.setVolumeProvider_impl(volumeProvider);
    592             return (U) this;
    593         }
    594 
    595         /**
    596          * Set an intent for launching UI for this Session. This can be used as a
    597          * quick link to an ongoing media screen. The intent should be for an
    598          * activity that may be started using {@link Context#startActivity(Intent)}.
    599          *
    600          * @param pi The intent to launch to show UI for this session.
    601          */
    602         @NonNull U setSessionActivity(@Nullable PendingIntent pi) {
    603             mProvider.setSessionActivity_impl(pi);
    604             return (U) this;
    605         }
    606 
    607         /**
    608          * Set ID of the session. If it's not set, an empty string with used to create a session.
    609          * <p>
    610          * Use this if and only if your app supports multiple playback at the same time and also
    611          * wants to provide external apps to have finer controls of them.
    612          *
    613          * @param id id of the session. Must be unique per package.
    614          * @throws IllegalArgumentException if id is {@code null}
    615          * @return
    616          */
    617         @NonNull U setId(@NonNull String id) {
    618             mProvider.setId_impl(id);
    619             return (U) this;
    620         }
    621 
    622         /**
    623          * Set callback for the session.
    624          *
    625          * @param executor callback executor
    626          * @param callback session callback.
    627          * @return
    628          */
    629         @NonNull U setSessionCallback(@NonNull @CallbackExecutor Executor executor,
    630                 @NonNull C callback) {
    631             mProvider.setSessionCallback_impl(executor, callback);
    632             return (U) this;
    633         }
    634 
    635         /**
    636          * Build {@link MediaSession2}.
    637          *
    638          * @return a new session
    639          * @throws IllegalStateException if the session with the same id is already exists for the
    640          *      package.
    641          */
    642         @NonNull T build() {
    643             return mProvider.build_impl();
    644         }
    645     }
    646 
    647     /**
    648      * Builder for {@link MediaSession2}.
    649      * <p>
    650      * Any incoming event from the {@link MediaController2} will be handled on the thread
    651      * that created session with the {@link Builder#build()}.
    652      */
    653     // Override all methods just to show them with the type instead of generics in Javadoc.
    654     // This workarounds javadoc issue described in the MediaSession2.BuilderBase.
    655     public static final class Builder extends BuilderBase<MediaSession2, Builder, SessionCallback> {
    656         public Builder(Context context) {
    657             super((instance) -> ApiLoader.getProvider().createMediaSession2Builder(
    658                     context, (Builder) instance));
    659         }
    660 
    661         @Override
    662         public @NonNull Builder setPlayer(@NonNull MediaPlayerBase player) {
    663             return super.setPlayer(player);
    664         }
    665 
    666         @Override
    667         public Builder setPlaylistAgent(@NonNull MediaPlaylistAgent playlistAgent) {
    668             return super.setPlaylistAgent(playlistAgent);
    669         }
    670 
    671         @Override
    672         public @NonNull Builder setVolumeProvider(@Nullable VolumeProvider2 volumeProvider) {
    673             return super.setVolumeProvider(volumeProvider);
    674         }
    675 
    676         @Override
    677         public @NonNull Builder setSessionActivity(@Nullable PendingIntent pi) {
    678             return super.setSessionActivity(pi);
    679         }
    680 
    681         @Override
    682         public @NonNull Builder setId(@NonNull String id) {
    683             return super.setId(id);
    684         }
    685 
    686         @Override
    687         public @NonNull Builder setSessionCallback(@NonNull Executor executor,
    688                 @Nullable SessionCallback callback) {
    689             return super.setSessionCallback(executor, callback);
    690         }
    691 
    692         @Override
    693         public @NonNull MediaSession2 build() {
    694             return super.build();
    695         }
    696     }
    697 
    698     /**
    699      * Information of a controller.
    700      */
    701     public static final class ControllerInfo {
    702         private final ControllerInfoProvider mProvider;
    703 
    704         /**
    705          * @hide
    706          */
    707         public ControllerInfo(@NonNull Context context, int uid, int pid,
    708                 @NonNull String packageName, @NonNull IInterface callback) {
    709             mProvider = ApiLoader.getProvider().createMediaSession2ControllerInfo(
    710                     context, this, uid, pid, packageName, callback);
    711         }
    712 
    713         /**
    714          * @return package name of the controller
    715          */
    716         public @NonNull String getPackageName() {
    717             return mProvider.getPackageName_impl();
    718         }
    719 
    720         /**
    721          * @return uid of the controller
    722          */
    723         public int getUid() {
    724             return mProvider.getUid_impl();
    725         }
    726 
    727         /**
    728          * Return if the controller has granted {@code android.permission.MEDIA_CONTENT_CONTROL} or
    729          * has a enabled notification listener so can be trusted to accept connection and incoming
    730          * command request.
    731          *
    732          * @return {@code true} if the controller is trusted.
    733          */
    734         public boolean isTrusted() {
    735             return mProvider.isTrusted_impl();
    736         }
    737 
    738         /**
    739          * @hide
    740          */
    741         public @NonNull ControllerInfoProvider getProvider() {
    742             return mProvider;
    743         }
    744 
    745         @Override
    746         public int hashCode() {
    747             return mProvider.hashCode_impl();
    748         }
    749 
    750         @Override
    751         public boolean equals(Object obj) {
    752             return mProvider.equals_impl(obj);
    753         }
    754 
    755         @Override
    756         public String toString() {
    757             return mProvider.toString_impl();
    758         }
    759     }
    760 
    761     /**
    762      * Button for a {@link SessionCommand2} that will be shown by the controller.
    763      * <p>
    764      * It's up to the controller's decision to respect or ignore this customization request.
    765      */
    766     public static final class CommandButton {
    767         private final CommandButtonProvider mProvider;
    768 
    769         /**
    770          * @hide
    771          */
    772         public CommandButton(CommandButtonProvider provider) {
    773             mProvider = provider;
    774         }
    775 
    776         /**
    777          * Get command associated with this button. Can be {@code null} if the button isn't enabled
    778          * and only providing placeholder.
    779          *
    780          * @return command or {@code null}
    781          */
    782         public @Nullable
    783         SessionCommand2 getCommand() {
    784             return mProvider.getCommand_impl();
    785         }
    786 
    787         /**
    788          * Resource id of the button in this package. Can be {@code 0} if the command is predefined
    789          * and custom icon isn't needed.
    790          *
    791          * @return resource id of the icon. Can be {@code 0}.
    792          */
    793         public int getIconResId() {
    794             return mProvider.getIconResId_impl();
    795         }
    796 
    797         /**
    798          * Display name of the button. Can be {@code null} or empty if the command is predefined
    799          * and custom name isn't needed.
    800          *
    801          * @return custom display name. Can be {@code null} or empty.
    802          */
    803         public @Nullable String getDisplayName() {
    804             return mProvider.getDisplayName_impl();
    805         }
    806 
    807         /**
    808          * Extra information of the button. It's private information between session and controller.
    809          *
    810          * @return
    811          */
    812         public @Nullable Bundle getExtras() {
    813             return mProvider.getExtras_impl();
    814         }
    815 
    816         /**
    817          * Return whether it's enabled
    818          *
    819          * @return {@code true} if enabled. {@code false} otherwise.
    820          */
    821         public boolean isEnabled() {
    822             return mProvider.isEnabled_impl();
    823         }
    824 
    825         /**
    826          * @hide
    827          */
    828         public @NonNull CommandButtonProvider getProvider() {
    829             return mProvider;
    830         }
    831 
    832         /**
    833          * Builder for {@link CommandButton}.
    834          */
    835         public static final class Builder {
    836             private final CommandButtonProvider.BuilderProvider mProvider;
    837 
    838             public Builder() {
    839                 mProvider = ApiLoader.getProvider().createMediaSession2CommandButtonBuilder(this);
    840             }
    841 
    842             public @NonNull Builder setCommand(@Nullable SessionCommand2 command) {
    843                 return mProvider.setCommand_impl(command);
    844             }
    845 
    846             public @NonNull Builder setIconResId(int resId) {
    847                 return mProvider.setIconResId_impl(resId);
    848             }
    849 
    850             public @NonNull Builder setDisplayName(@Nullable String displayName) {
    851                 return mProvider.setDisplayName_impl(displayName);
    852             }
    853 
    854             public @NonNull Builder setEnabled(boolean enabled) {
    855                 return mProvider.setEnabled_impl(enabled);
    856             }
    857 
    858             public @NonNull Builder setExtras(@Nullable Bundle extras) {
    859                 return mProvider.setExtras_impl(extras);
    860             }
    861 
    862             public @NonNull CommandButton build() {
    863                 return mProvider.build_impl();
    864             }
    865         }
    866     }
    867 
    868     /**
    869      * Constructor is hidden and apps can only instantiate indirectly through {@link Builder}.
    870      * <p>
    871      * This intended behavior and here's the reasons.
    872      *    1. Prevent multiple sessions with the same tag in a media app.
    873      *       Whenever it happens only one session was properly setup and others were all dummies.
    874      *       Android framework couldn't find the right session to dispatch media key event.
    875      *    2. Simplify session's lifecycle.
    876      *       {@link android.media.session.MediaSession} is available after all of
    877      *       {@link android.media.session.MediaSession#setFlags(int)},
    878      *       {@link android.media.session.MediaSession#setCallback(
    879      *              android.media.session.MediaSession.Callback)},
    880      *       and {@link android.media.session.MediaSession#setActive(boolean)}.
    881      *       It was common for an app to omit one, so framework had to add heuristics to figure out
    882      *       which should be the highest priority for handling media key event.
    883      * @hide
    884      */
    885     public MediaSession2(MediaSession2Provider provider) {
    886         super();
    887         mProvider = provider;
    888     }
    889 
    890     /**
    891      * @hide
    892      */
    893     public @NonNull MediaSession2Provider getProvider() {
    894         return mProvider;
    895     }
    896 
    897     /**
    898      * Sets the underlying {@link MediaPlayerBase} and {@link MediaPlaylistAgent} for this session
    899      * to dispatch incoming event to.
    900      * <p>
    901      * When a {@link MediaPlaylistAgent} is specified here, the playlist agent should manage
    902      * {@link MediaPlayerBase} for calling {@link MediaPlayerBase#setNextDataSources(List)}.
    903      * <p>
    904      * If the {@link MediaPlaylistAgent} isn't set, session will recreate the default playlist
    905      * agent.
    906      *
    907      * @param player a {@link MediaPlayerBase} that handles actual media playback in your app
    908      * @param playlistAgent a {@link MediaPlaylistAgent} that manages playlist of the {@code player}
    909      * @param volumeProvider a {@link VolumeProvider2}. If {@code null}, system will adjust the
    910      *                       appropriate stream volume for this session's player.
    911      */
    912     public void updatePlayer(@NonNull MediaPlayerBase player,
    913             @Nullable MediaPlaylistAgent playlistAgent, @Nullable VolumeProvider2 volumeProvider) {
    914         mProvider.updatePlayer_impl(player, playlistAgent, volumeProvider);
    915     }
    916 
    917     @Override
    918     public void close() {
    919         mProvider.close_impl();
    920     }
    921 
    922     /**
    923      * @return player
    924      */
    925     public @NonNull MediaPlayerBase getPlayer() {
    926         return mProvider.getPlayer_impl();
    927     }
    928 
    929     /**
    930      * @return playlist agent
    931      */
    932     public @NonNull MediaPlaylistAgent getPlaylistAgent() {
    933         return mProvider.getPlaylistAgent_impl();
    934     }
    935 
    936     /**
    937      * @return volume provider
    938      */
    939     public @Nullable VolumeProvider2 getVolumeProvider() {
    940         return mProvider.getVolumeProvider_impl();
    941     }
    942 
    943     /**
    944      * Returns the {@link SessionToken2} for creating {@link MediaController2}.
    945      */
    946     public @NonNull
    947     SessionToken2 getToken() {
    948         return mProvider.getToken_impl();
    949     }
    950 
    951     public @NonNull List<ControllerInfo> getConnectedControllers() {
    952         return mProvider.getConnectedControllers_impl();
    953     }
    954 
    955     /**
    956      * Set the {@link AudioFocusRequest} to obtain the audio focus
    957      *
    958      * @param afr the full request parameters
    959      */
    960     public void setAudioFocusRequest(@Nullable AudioFocusRequest afr) {
    961         // TODO(jaewan): implement this (b/72529899)
    962         // mProvider.setAudioFocusRequest_impl(focusGain);
    963     }
    964 
    965     /**
    966      * Sets ordered list of {@link CommandButton} for controllers to build UI with it.
    967      * <p>
    968      * It's up to controller's decision how to represent the layout in its own UI.
    969      * Here's the same way
    970      * (layout[i] means a CommandButton at index i in the given list)
    971      * For 5 icons row
    972      *      layout[3] layout[1] layout[0] layout[2] layout[4]
    973      * For 3 icons row
    974      *      layout[1] layout[0] layout[2]
    975      * For 5 icons row with overflow icon (can show +5 extra buttons with overflow button)
    976      *      expanded row:   layout[5] layout[6] layout[7] layout[8] layout[9]
    977      *      main row:       layout[3] layout[1] layout[0] layout[2] layout[4]
    978      * <p>
    979      * This API can be called in the {@link SessionCallback#onConnect(
    980      * MediaSession2, ControllerInfo)}.
    981      *
    982      * @param controller controller to specify layout.
    983      * @param layout ordered list of layout.
    984      */
    985     public void setCustomLayout(@NonNull ControllerInfo controller,
    986             @NonNull List<CommandButton> layout) {
    987         mProvider.setCustomLayout_impl(controller, layout);
    988     }
    989 
    990     /**
    991      * Set the new allowed command group for the controller
    992      *
    993      * @param controller controller to change allowed commands
    994      * @param commands new allowed commands
    995      */
    996     public void setAllowedCommands(@NonNull ControllerInfo controller,
    997             @NonNull SessionCommandGroup2 commands) {
    998         mProvider.setAllowedCommands_impl(controller, commands);
    999     }
   1000 
   1001     /**
   1002      * Send custom command to all connected controllers.
   1003      *
   1004      * @param command a command
   1005      * @param args optional argument
   1006      */
   1007     public void sendCustomCommand(@NonNull SessionCommand2 command, @Nullable Bundle args) {
   1008         mProvider.sendCustomCommand_impl(command, args);
   1009     }
   1010 
   1011     /**
   1012      * Send custom command to a specific controller.
   1013      *
   1014      * @param command a command
   1015      * @param args optional argument
   1016      * @param receiver result receiver for the session
   1017      */
   1018     public void sendCustomCommand(@NonNull ControllerInfo controller,
   1019             @NonNull SessionCommand2 command, @Nullable Bundle args,
   1020             @Nullable ResultReceiver receiver) {
   1021         // Equivalent to the MediaController.sendCustomCommand(Action action, ResultReceiver r);
   1022         mProvider.sendCustomCommand_impl(controller, command, args, receiver);
   1023     }
   1024 
   1025     /**
   1026      * Play playback
   1027      * <p>
   1028      * This calls {@link MediaPlayerBase#play()}.
   1029      */
   1030     public void play() {
   1031         mProvider.play_impl();
   1032     }
   1033 
   1034     /**
   1035      * Pause playback.
   1036      * <p>
   1037      * This calls {@link MediaPlayerBase#pause()}.
   1038      */
   1039     public void pause() {
   1040         mProvider.pause_impl();
   1041     }
   1042 
   1043     /**
   1044      * Stop playback, and reset the player to the initial state.
   1045      * <p>
   1046      * This calls {@link MediaPlayerBase#reset()}.
   1047      */
   1048     public void stop() {
   1049         mProvider.stop_impl();
   1050     }
   1051 
   1052     /**
   1053      * Request that the player prepare its playback. In other words, other sessions can continue
   1054      * to play during the preparation of this session. This method can be used to speed up the
   1055      * start of the playback. Once the preparation is done, the session will change its playback
   1056      * state to {@link MediaPlayerBase#PLAYER_STATE_PAUSED}. Afterwards, {@link #play} can be called
   1057      * to start playback.
   1058      * <p>
   1059      * This calls {@link MediaPlayerBase#reset()}.
   1060      */
   1061     public void prepare() {
   1062         mProvider.prepare_impl();
   1063     }
   1064 
   1065     /**
   1066      * Move to a new location in the media stream.
   1067      *
   1068      * @param pos Position to move to, in milliseconds.
   1069      */
   1070     public void seekTo(long pos) {
   1071         mProvider.seekTo_impl(pos);
   1072     }
   1073 
   1074     /**
   1075      * @hide
   1076      */
   1077     public void skipForward() {
   1078         // To match with KEYCODE_MEDIA_SKIP_FORWARD
   1079     }
   1080 
   1081     /**
   1082      * @hide
   1083      */
   1084     public void skipBackward() {
   1085         // To match with KEYCODE_MEDIA_SKIP_BACKWARD
   1086     }
   1087 
   1088     /**
   1089      * Notify errors to the connected controllers
   1090      *
   1091      * @param errorCode error code
   1092      * @param extras extras
   1093      */
   1094     public void notifyError(@ErrorCode int errorCode, @Nullable Bundle extras) {
   1095         mProvider.notifyError_impl(errorCode, extras);
   1096     }
   1097 
   1098     /**
   1099      * Gets the current player state.
   1100      *
   1101      * @return the current player state
   1102      */
   1103     public @PlayerState int getPlayerState() {
   1104         return mProvider.getPlayerState_impl();
   1105     }
   1106 
   1107     /**
   1108      * Gets the current position.
   1109      *
   1110      * @return the current playback position in ms, or {@link MediaPlayerBase#UNKNOWN_TIME} if
   1111      *         unknown.
   1112      */
   1113     public long getCurrentPosition() {
   1114         return mProvider.getCurrentPosition_impl();
   1115     }
   1116 
   1117     /**
   1118      * Gets the buffered position, or {@link MediaPlayerBase#UNKNOWN_TIME} if unknown.
   1119      *
   1120      * @return the buffered position in ms, or {@link MediaPlayerBase#UNKNOWN_TIME}.
   1121      */
   1122     public long getBufferedPosition() {
   1123         return mProvider.getBufferedPosition_impl();
   1124     }
   1125 
   1126     /**
   1127      * Gets the current buffering state of the player.
   1128      * During buffering, see {@link #getBufferedPosition()} for the quantifying the amount already
   1129      * buffered.
   1130      *
   1131      * @return the buffering state.
   1132      */
   1133     public @BuffState int getBufferingState() {
   1134         // TODO(jaewan): Implement this
   1135         return BUFFERING_STATE_UNKNOWN;
   1136     }
   1137 
   1138     /**
   1139      * Get the playback speed.
   1140      *
   1141      * @return speed
   1142      */
   1143     public float getPlaybackSpeed() {
   1144         // TODO(jaewan): implement this (b/74093080)
   1145         return -1;
   1146     }
   1147 
   1148     /**
   1149      * Set the playback speed.
   1150      */
   1151     public void setPlaybackSpeed(float speed) {
   1152         // TODO(jaewan): implement this (b/74093080)
   1153     }
   1154 
   1155     /**
   1156      * Sets the data source missing helper. Helper will be used to provide default implementation of
   1157      * {@link MediaPlaylistAgent} when it isn't set by developer.
   1158      * <p>
   1159      * Default implementation of the {@link MediaPlaylistAgent} will call helper when a
   1160      * {@link MediaItem2} in the playlist doesn't have a {@link DataSourceDesc}. This may happen
   1161      * when
   1162      * <ul>
   1163      *      <li>{@link MediaItem2} specified by {@link #setPlaylist(List, MediaMetadata2)} doesn't
   1164      *          have {@link DataSourceDesc}</li>
   1165      *      <li>{@link MediaController2#addPlaylistItem(int, MediaItem2)} is called and accepted
   1166      *          by {@link SessionCallback#onCommandRequest(
   1167      *          MediaSession2, ControllerInfo, SessionCommand2)}.
   1168      *          In that case, an item would be added automatically without the data source.</li>
   1169      * </ul>
   1170      * <p>
   1171      * If it's not set, playback wouldn't happen for the item without data source descriptor.
   1172      * <p>
   1173      * The helper will be run on the executor that was specified by
   1174      * {@link Builder#setSessionCallback(Executor, SessionCallback)}.
   1175      *
   1176      * @param helper a data source missing helper.
   1177      * @throws IllegalStateException when the helper is set when the playlist agent is set
   1178      * @see #setPlaylist(List, MediaMetadata2)
   1179      * @see SessionCallback#onCommandRequest(MediaSession2, ControllerInfo, SessionCommand2)
   1180      * @see SessionCommand2#COMMAND_CODE_PLAYLIST_ADD_ITEM
   1181      * @see SessionCommand2#COMMAND_CODE_PLAYLIST_REPLACE_ITEM
   1182      */
   1183     public void setOnDataSourceMissingHelper(@NonNull OnDataSourceMissingHelper helper) {
   1184         mProvider.setOnDataSourceMissingHelper_impl(helper);
   1185     }
   1186 
   1187     /**
   1188      * Clears the data source missing helper.
   1189      *
   1190      * @see #setOnDataSourceMissingHelper(OnDataSourceMissingHelper)
   1191      */
   1192     public void clearOnDataSourceMissingHelper() {
   1193         mProvider.clearOnDataSourceMissingHelper_impl();
   1194     }
   1195 
   1196     /**
   1197      * Returns the playlist from the {@link MediaPlaylistAgent}.
   1198      * <p>
   1199      * This list may differ with the list that was specified with
   1200      * {@link #setPlaylist(List, MediaMetadata2)} depending on the {@link MediaPlaylistAgent}
   1201      * implementation. Use media items returned here for other playlist agent APIs such as
   1202      * {@link MediaPlaylistAgent#skipToPlaylistItem(MediaItem2)}.
   1203      *
   1204      * @return playlist
   1205      * @see MediaPlaylistAgent#getPlaylist()
   1206      * @see SessionCallback#onPlaylistChanged(
   1207      *          MediaSession2, MediaPlaylistAgent, List, MediaMetadata2)
   1208      */
   1209     public List<MediaItem2> getPlaylist() {
   1210         return mProvider.getPlaylist_impl();
   1211     }
   1212 
   1213     /**
   1214      * Sets a list of {@link MediaItem2} to the {@link MediaPlaylistAgent}. Ensure uniqueness of
   1215      * each {@link MediaItem2} in the playlist so the session can uniquely identity individual
   1216      * items.
   1217      * <p>
   1218      * This may be an asynchronous call, and {@link MediaPlaylistAgent} may keep the copy of the
   1219      * list. Wait for {@link SessionCallback#onPlaylistChanged(MediaSession2, MediaPlaylistAgent,
   1220      * List, MediaMetadata2)} to know the operation finishes.
   1221      * <p>
   1222      * You may specify a {@link MediaItem2} without {@link DataSourceDesc}. In that case,
   1223      * {@link MediaPlaylistAgent} has responsibility to dynamically query {@link DataSourceDesc}
   1224      * when such media item is ready for preparation or play. Default implementation needs
   1225      * {@link OnDataSourceMissingHelper} for such case.
   1226      *
   1227      * @param list A list of {@link MediaItem2} objects to set as a play list.
   1228      * @throws IllegalArgumentException if given list is {@code null}, or has duplicated media
   1229      * items.
   1230      * @see MediaPlaylistAgent#setPlaylist(List, MediaMetadata2)
   1231      * @see SessionCallback#onPlaylistChanged(
   1232      *          MediaSession2, MediaPlaylistAgent, List, MediaMetadata2)
   1233      * @see #setOnDataSourceMissingHelper
   1234      */
   1235     public void setPlaylist(@NonNull List<MediaItem2> list, @Nullable MediaMetadata2 metadata) {
   1236         mProvider.setPlaylist_impl(list, metadata);
   1237     }
   1238 
   1239     /**
   1240      * Skips to the item in the playlist.
   1241      * <p>
   1242      * This calls {@link MediaPlaylistAgent#skipToPlaylistItem(MediaItem2)} and the behavior depends
   1243      * on the playlist agent implementation, especially with the shuffle/repeat mode.
   1244      *
   1245      * @param item The item in the playlist you want to play
   1246      * @see #getShuffleMode()
   1247      * @see #getRepeatMode()
   1248      */
   1249     public void skipToPlaylistItem(@NonNull MediaItem2 item) {
   1250         mProvider.skipToPlaylistItem_impl(item);
   1251     }
   1252 
   1253     /**
   1254      * Skips to the previous item.
   1255      * <p>
   1256      * This calls {@link MediaPlaylistAgent#skipToPreviousItem()} and the behavior depends on the
   1257      * playlist agent implementation, especially with the shuffle/repeat mode.
   1258      *
   1259      * @see #getShuffleMode()
   1260      * @see #getRepeatMode()
   1261      **/
   1262     public void skipToPreviousItem() {
   1263         mProvider.skipToPreviousItem_impl();
   1264     }
   1265 
   1266     /**
   1267      * Skips to the next item.
   1268      * <p>
   1269      * This calls {@link MediaPlaylistAgent#skipToNextItem()} and the behavior depends on the
   1270      * playlist agent implementation, especially with the shuffle/repeat mode.
   1271      *
   1272      * @see #getShuffleMode()
   1273      * @see #getRepeatMode()
   1274      */
   1275     public void skipToNextItem() {
   1276         mProvider.skipToNextItem_impl();
   1277     }
   1278 
   1279     /**
   1280      * Gets the playlist metadata from the {@link MediaPlaylistAgent}.
   1281      *
   1282      * @return the playlist metadata
   1283      */
   1284     public MediaMetadata2 getPlaylistMetadata() {
   1285         return mProvider.getPlaylistMetadata_impl();
   1286     }
   1287 
   1288     /**
   1289      * Adds the media item to the playlist at position index. Index equals or greater than
   1290      * the current playlist size will add the item at the end of the playlist.
   1291      * <p>
   1292      * This will not change the currently playing media item.
   1293      * If index is less than or equal to the current index of the play list,
   1294      * the current index of the play list will be incremented correspondingly.
   1295      *
   1296      * @param index the index you want to add
   1297      * @param item the media item you want to add
   1298      */
   1299     public void addPlaylistItem(int index, @NonNull MediaItem2 item) {
   1300         mProvider.addPlaylistItem_impl(index, item);
   1301     }
   1302 
   1303     /**
   1304      * Removes the media item in the playlist.
   1305      * <p>
   1306      * If the item is the currently playing item of the playlist, current playback
   1307      * will be stopped and playback moves to next source in the list.
   1308      *
   1309      * @param item the media item you want to add
   1310      */
   1311     public void removePlaylistItem(@NonNull MediaItem2 item) {
   1312         mProvider.removePlaylistItem_impl(item);
   1313     }
   1314 
   1315     /**
   1316      * Replaces the media item at index in the playlist. This can be also used to update metadata of
   1317      * an item.
   1318      *
   1319      * @param index the index of the item to replace
   1320      * @param item the new item
   1321      */
   1322     public void replacePlaylistItem(int index, @NonNull MediaItem2 item) {
   1323         mProvider.replacePlaylistItem_impl(index, item);
   1324     }
   1325 
   1326     /**
   1327      * Return currently playing media item.
   1328      *
   1329      * @return currently playing media item
   1330      */
   1331     public MediaItem2 getCurrentMediaItem() {
   1332         // TODO(jaewan): Rename provider, and implement (b/74316764)
   1333         return mProvider.getCurrentPlaylistItem_impl();
   1334     }
   1335 
   1336     /**
   1337      * Updates the playlist metadata to the {@link MediaPlaylistAgent}.
   1338      *
   1339      * @param metadata metadata of the playlist
   1340      */
   1341     public void updatePlaylistMetadata(@Nullable MediaMetadata2 metadata) {
   1342         mProvider.updatePlaylistMetadata_impl(metadata);
   1343     }
   1344 
   1345     /**
   1346      * Gets the repeat mode from the {@link MediaPlaylistAgent}.
   1347      *
   1348      * @return repeat mode
   1349      * @see MediaPlaylistAgent#REPEAT_MODE_NONE
   1350      * @see MediaPlaylistAgent#REPEAT_MODE_ONE
   1351      * @see MediaPlaylistAgent#REPEAT_MODE_ALL
   1352      * @see MediaPlaylistAgent#REPEAT_MODE_GROUP
   1353      */
   1354     public @RepeatMode int getRepeatMode() {
   1355         return mProvider.getRepeatMode_impl();
   1356     }
   1357 
   1358     /**
   1359      * Sets the repeat mode to the {@link MediaPlaylistAgent}.
   1360      *
   1361      * @param repeatMode repeat mode
   1362      * @see MediaPlaylistAgent#REPEAT_MODE_NONE
   1363      * @see MediaPlaylistAgent#REPEAT_MODE_ONE
   1364      * @see MediaPlaylistAgent#REPEAT_MODE_ALL
   1365      * @see MediaPlaylistAgent#REPEAT_MODE_GROUP
   1366      */
   1367     public void setRepeatMode(@RepeatMode int repeatMode) {
   1368         mProvider.setRepeatMode_impl(repeatMode);
   1369     }
   1370 
   1371     /**
   1372      * Gets the shuffle mode from the {@link MediaPlaylistAgent}.
   1373      *
   1374      * @return The shuffle mode
   1375      * @see MediaPlaylistAgent#SHUFFLE_MODE_NONE
   1376      * @see MediaPlaylistAgent#SHUFFLE_MODE_ALL
   1377      * @see MediaPlaylistAgent#SHUFFLE_MODE_GROUP
   1378      */
   1379     public @ShuffleMode int getShuffleMode() {
   1380         return mProvider.getShuffleMode_impl();
   1381     }
   1382 
   1383     /**
   1384      * Sets the shuffle mode to the {@link MediaPlaylistAgent}.
   1385      *
   1386      * @param shuffleMode The shuffle mode
   1387      * @see MediaPlaylistAgent#SHUFFLE_MODE_NONE
   1388      * @see MediaPlaylistAgent#SHUFFLE_MODE_ALL
   1389      * @see MediaPlaylistAgent#SHUFFLE_MODE_GROUP
   1390      */
   1391     public void setShuffleMode(@ShuffleMode int shuffleMode) {
   1392         mProvider.setShuffleMode_impl(shuffleMode);
   1393     }
   1394 }
   1395