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 androidx.media;
     18 
     19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
     20 
     21 import android.annotation.TargetApi;
     22 import android.app.PendingIntent;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.media.AudioFocusRequest;
     26 import android.net.Uri;
     27 import android.os.Build;
     28 import android.os.Bundle;
     29 import android.os.IBinder;
     30 import android.os.RemoteException;
     31 import android.os.ResultReceiver;
     32 import android.support.v4.media.session.MediaSessionCompat;
     33 import android.support.v4.media.session.PlaybackStateCompat;
     34 
     35 import androidx.annotation.IntDef;
     36 import androidx.annotation.NonNull;
     37 import androidx.annotation.Nullable;
     38 import androidx.annotation.RestrictTo;
     39 import androidx.media.MediaController2.PlaybackInfo;
     40 import androidx.media.MediaPlayerInterface.BuffState;
     41 import androidx.media.MediaPlayerInterface.PlayerState;
     42 import androidx.media.MediaPlaylistAgent.PlaylistEventCallback;
     43 import androidx.media.MediaPlaylistAgent.RepeatMode;
     44 import androidx.media.MediaPlaylistAgent.ShuffleMode;
     45 
     46 import java.lang.annotation.Retention;
     47 import java.lang.annotation.RetentionPolicy;
     48 import java.util.List;
     49 import java.util.concurrent.Executor;
     50 
     51 /**
     52  * Allows a media app to expose its transport controls and playback information in a process to
     53  * other processes including the Android framework and other apps. Common use cases are as follows.
     54  * <ul>
     55  *     <li>Bluetooth/wired headset key events support</li>
     56  *     <li>Android Auto/Wearable support</li>
     57  *     <li>Separating UI process and playback process</li>
     58  * </ul>
     59  * <p>
     60  * A MediaSession2 should be created when an app wants to publish media playback information or
     61  * handle media keys. In general an app only needs one session for all playback, though multiple
     62  * sessions can be created to provide finer grain controls of media.
     63  * <p>
     64  * If you want to support background playback, {@link MediaSessionService2} is preferred
     65  * instead. With it, your playback can be revived even after playback is finished. See
     66  * {@link MediaSessionService2} for details.
     67  * <p>
     68  * A session can be obtained by {@link Builder}. The owner of the session may pass its session token
     69  * to other processes to allow them to create a {@link MediaController2} to interact with the
     70  * session.
     71  * <p>
     72  * When a session receive transport control commands, the session sends the commands directly to
     73  * the the underlying media player set by {@link Builder} or
     74  * {@link #updatePlayer}.
     75  * <p>
     76  * When an app is finished performing playback it must call {@link #close()} to clean up the session
     77  * and notify any controllers.
     78  * <p>
     79  * {@link MediaSession2} objects should be used on the thread on the looper.
     80  *
     81  * @see MediaSessionService2
     82  */
     83 @TargetApi(Build.VERSION_CODES.KITKAT)
     84 public class MediaSession2 extends MediaInterface2.SessionPlayer implements AutoCloseable {
     85     /**
     86      * @hide
     87      */
     88     @RestrictTo(LIBRARY_GROUP)
     89     @IntDef({ERROR_CODE_UNKNOWN_ERROR, ERROR_CODE_APP_ERROR, ERROR_CODE_NOT_SUPPORTED,
     90             ERROR_CODE_AUTHENTICATION_EXPIRED, ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED,
     91             ERROR_CODE_CONCURRENT_STREAM_LIMIT, ERROR_CODE_PARENTAL_CONTROL_RESTRICTED,
     92             ERROR_CODE_NOT_AVAILABLE_IN_REGION, ERROR_CODE_CONTENT_ALREADY_PLAYING,
     93             ERROR_CODE_SKIP_LIMIT_REACHED, ERROR_CODE_ACTION_ABORTED, ERROR_CODE_END_OF_QUEUE,
     94             ERROR_CODE_SETUP_REQUIRED})
     95     @Retention(RetentionPolicy.SOURCE)
     96     public @interface ErrorCode {}
     97 
     98     /**
     99      * This is the default error code and indicates that none of the other error codes applies.
    100      */
    101     public static final int ERROR_CODE_UNKNOWN_ERROR = 0;
    102 
    103     /**
    104      * Error code when the application state is invalid to fulfill the request.
    105      */
    106     public static final int ERROR_CODE_APP_ERROR = 1;
    107 
    108     /**
    109      * Error code when the request is not supported by the application.
    110      */
    111     public static final int ERROR_CODE_NOT_SUPPORTED = 2;
    112 
    113     /**
    114      * Error code when the request cannot be performed because authentication has expired.
    115      */
    116     public static final int ERROR_CODE_AUTHENTICATION_EXPIRED = 3;
    117 
    118     /**
    119      * Error code when a premium account is required for the request to succeed.
    120      */
    121     public static final int ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED = 4;
    122 
    123     /**
    124      * Error code when too many concurrent streams are detected.
    125      */
    126     public static final int ERROR_CODE_CONCURRENT_STREAM_LIMIT = 5;
    127 
    128     /**
    129      * Error code when the content is blocked due to parental controls.
    130      */
    131     public static final int ERROR_CODE_PARENTAL_CONTROL_RESTRICTED = 6;
    132 
    133     /**
    134      * Error code when the content is blocked due to being regionally unavailable.
    135      */
    136     public static final int ERROR_CODE_NOT_AVAILABLE_IN_REGION = 7;
    137 
    138     /**
    139      * Error code when the requested content is already playing.
    140      */
    141     public static final int ERROR_CODE_CONTENT_ALREADY_PLAYING = 8;
    142 
    143     /**
    144      * Error code when the application cannot skip any more songs because skip limit is reached.
    145      */
    146     public static final int ERROR_CODE_SKIP_LIMIT_REACHED = 9;
    147 
    148     /**
    149      * Error code when the action is interrupted due to some external event.
    150      */
    151     public static final int ERROR_CODE_ACTION_ABORTED = 10;
    152 
    153     /**
    154      * Error code when the playback navigation (previous, next) is not possible because the queue
    155      * was exhausted.
    156      */
    157     public static final int ERROR_CODE_END_OF_QUEUE = 11;
    158 
    159     /**
    160      * Error code when the session needs user's manual intervention.
    161      */
    162     public static final int ERROR_CODE_SETUP_REQUIRED = 12;
    163 
    164     static final String TAG = "MediaSession2";
    165 
    166     private final SupportLibraryImpl mImpl;
    167 
    168     MediaSession2(SupportLibraryImpl impl) {
    169         mImpl = impl;
    170     }
    171 
    172     SupportLibraryImpl getImpl() {
    173         return mImpl;
    174     }
    175 
    176     /**
    177      * Sets the underlying {@link MediaPlayerInterface} and {@link MediaPlaylistAgent} for this
    178      * session to dispatch incoming event to.
    179      * <p>
    180      * When a {@link MediaPlaylistAgent} is specified here, the playlist agent should manage
    181      * {@link MediaPlayerInterface} for calling
    182      * {@link MediaPlayerInterface#setNextDataSources(List)}.
    183      * <p>
    184      * If the {@link MediaPlaylistAgent} isn't set, session will recreate the default playlist
    185      * agent.
    186      *
    187      * @param player a {@link MediaPlayerInterface} that handles actual media playback in your app
    188      * @param playlistAgent a {@link MediaPlaylistAgent} that manages playlist of the {@code player}
    189      * @param volumeProvider a {@link VolumeProviderCompat}. If {@code null}, system will adjust the
    190      *                       appropriate stream volume for this session's player.
    191      */
    192     public void updatePlayer(@NonNull MediaPlayerInterface player,
    193             @Nullable MediaPlaylistAgent playlistAgent,
    194             @Nullable VolumeProviderCompat volumeProvider) {
    195         mImpl.updatePlayer(player, playlistAgent, volumeProvider);
    196     }
    197 
    198     @Override
    199     public void close() {
    200         try {
    201             mImpl.close();
    202         } catch (Exception e) {
    203             // Should not be here.
    204         }
    205     }
    206 
    207     /**
    208      * @return player
    209      */
    210     public @NonNull MediaPlayerInterface getPlayer() {
    211         return mImpl.getPlayer();
    212     }
    213 
    214     /**
    215      * @return playlist agent
    216      */
    217     public @NonNull MediaPlaylistAgent getPlaylistAgent() {
    218         return mImpl.getPlaylistAgent();
    219     }
    220 
    221     /**
    222      * @return volume provider
    223      */
    224     public @Nullable VolumeProviderCompat getVolumeProvider() {
    225         return mImpl.getVolumeProvider();
    226     }
    227 
    228     /**
    229      * Returns the {@link SessionToken2} for creating {@link MediaController2}.
    230      */
    231     public @NonNull SessionToken2 getToken() {
    232         return mImpl.getToken();
    233     }
    234 
    235     @NonNull Context getContext() {
    236         return mImpl.getContext();
    237     }
    238 
    239     @NonNull Executor getCallbackExecutor() {
    240         return mImpl.getCallbackExecutor();
    241     }
    242 
    243     @NonNull SessionCallback getCallback() {
    244         return mImpl.getCallback();
    245     }
    246 
    247     /**
    248      * Returns the list of connected controller.
    249      *
    250      * @return list of {@link ControllerInfo}
    251      */
    252     public @NonNull List<ControllerInfo> getConnectedControllers() {
    253         return mImpl.getConnectedControllers();
    254     }
    255 
    256     /**
    257      * Set the {@link AudioFocusRequest} to obtain the audio focus
    258      *
    259      * @param afr the full request parameters
    260      */
    261     public void setAudioFocusRequest(@Nullable AudioFocusRequest afr) {
    262         mImpl.setAudioFocusRequest(afr);
    263     }
    264 
    265     /**
    266      * Sets ordered list of {@link CommandButton} for controllers to build UI with it.
    267      * <p>
    268      * It's up to controller's decision how to represent the layout in its own UI.
    269      * Here's the same way
    270      * (layout[i] means a CommandButton at index i in the given list)
    271      * For 5 icons row
    272      *      layout[3] layout[1] layout[0] layout[2] layout[4]
    273      * For 3 icons row
    274      *      layout[1] layout[0] layout[2]
    275      * For 5 icons row with overflow icon (can show +5 extra buttons with overflow button)
    276      *      expanded row:   layout[5] layout[6] layout[7] layout[8] layout[9]
    277      *      main row:       layout[3] layout[1] layout[0] layout[2] layout[4]
    278      * <p>
    279      * This API can be called in the
    280      * {@link SessionCallback#onConnect(MediaSession2, ControllerInfo)}.
    281      *
    282      * @param controller controller to specify layout.
    283      * @param layout ordered list of layout.
    284      */
    285     public void setCustomLayout(@NonNull ControllerInfo controller,
    286             @NonNull List<CommandButton> layout) {
    287         mImpl.setCustomLayout(controller, layout);
    288     }
    289 
    290     /**
    291      * Set the new allowed command group for the controller
    292      *
    293      * @param controller controller to change allowed commands
    294      * @param commands new allowed commands
    295      */
    296     public void setAllowedCommands(@NonNull ControllerInfo controller,
    297             @NonNull SessionCommandGroup2 commands) {
    298         mImpl.setAllowedCommands(controller, commands);
    299     }
    300 
    301     /**
    302      * Send custom command to all connected controllers.
    303      *
    304      * @param command a command
    305      * @param args optional argument
    306      */
    307     public void sendCustomCommand(@NonNull SessionCommand2 command, @Nullable Bundle args) {
    308         mImpl.sendCustomCommand(command, args);
    309     }
    310 
    311     /**
    312      * Send custom command to a specific controller.
    313      *
    314      * @param command a command
    315      * @param args optional argument
    316      * @param receiver result receiver for the session
    317      */
    318     public void sendCustomCommand(@NonNull ControllerInfo controller,
    319             @NonNull SessionCommand2 command, @Nullable Bundle args,
    320             @Nullable ResultReceiver receiver) {
    321         mImpl.sendCustomCommand(controller, command, args, receiver);
    322     }
    323 
    324     /**
    325      * Play playback.
    326      * <p>
    327      * This calls {@link MediaPlayerInterface#play()}.
    328      */
    329     @Override
    330     public void play() {
    331         mImpl.play();
    332     }
    333 
    334     /**
    335      * Pause playback.
    336      * <p>
    337      * This calls {@link MediaPlayerInterface#pause()}.
    338      */
    339     @Override
    340     public void pause() {
    341         mImpl.pause();
    342     }
    343 
    344     /**
    345      * Stop playback, and reset the player to the initial state.
    346      * <p>
    347      * This calls {@link MediaPlayerInterface#reset()}.
    348      */
    349     @Override
    350     public void reset() {
    351         mImpl.reset();
    352     }
    353 
    354     /**
    355      * Request that the player prepare its playback. In other words, other sessions can continue
    356      * to play during the preparation of this session. This method can be used to speed up the
    357      * start of the playback. Once the preparation is done, the session will change its playback
    358      * state to {@link MediaPlayerInterface#PLAYER_STATE_PAUSED}. Afterwards, {@link #play} can be
    359      * called to start playback.
    360      * <p>
    361      * This calls {@link MediaPlayerInterface#reset()}.
    362      */
    363     @Override
    364     public void prepare() {
    365         mImpl.prepare();
    366     }
    367 
    368     /**
    369      * Move to a new location in the media stream.
    370      *
    371      * @param pos Position to move to, in milliseconds.
    372      */
    373     @Override
    374     public void seekTo(long pos) {
    375         mImpl.seekTo(pos);
    376     }
    377 
    378     /**
    379      * @hide
    380      */
    381     @RestrictTo(LIBRARY_GROUP)
    382     @Override
    383     public void skipForward() {
    384         mImpl.skipForward();
    385     }
    386 
    387     /**
    388      * @hide
    389      */
    390     @RestrictTo(LIBRARY_GROUP)
    391     @Override
    392     public void skipBackward() {
    393         mImpl.skipBackward();
    394     }
    395 
    396     /**
    397      * Notify errors to the connected controllers
    398      *
    399      * @param errorCode error code
    400      * @param extras extras
    401      */
    402     @Override
    403     public void notifyError(@ErrorCode int errorCode, @Nullable Bundle extras) {
    404         mImpl.notifyError(errorCode, extras);
    405     }
    406 
    407     /**
    408      * Notify routes information to a connected controller
    409      *
    410      * @param controller controller information
    411      * @param routes The routes information. Each bundle should be from
    412      *              MediaRouteDescritor.asBundle().
    413      */
    414     public void notifyRoutesInfoChanged(@NonNull ControllerInfo controller,
    415             @Nullable List<Bundle> routes) {
    416         mImpl.notifyRoutesInfoChanged(controller, routes);
    417     }
    418 
    419     /**
    420      * Gets the current player state.
    421      *
    422      * @return the current player state
    423      */
    424     @Override
    425     public @PlayerState int getPlayerState() {
    426         return mImpl.getPlayerState();
    427     }
    428 
    429     /**
    430      * Gets the current position.
    431      *
    432      * @return the current playback position in ms, or {@link MediaPlayerInterface#UNKNOWN_TIME} if
    433      *         unknown.
    434      */
    435     @Override
    436     public long getCurrentPosition() {
    437         return mImpl.getCurrentPosition();
    438     }
    439 
    440     /**
    441      * Gets the duration of the currently playing media item.
    442      *
    443      * @return the duration of the current item from {@link MediaPlayerInterface#getDuration()}.
    444      */
    445     @Override
    446     public long getDuration() {
    447         return mImpl.getDuration();
    448     }
    449 
    450     /**
    451      * Gets the buffered position, or {@link MediaPlayerInterface#UNKNOWN_TIME} if unknown.
    452      *
    453      * @return the buffered position in ms, or {@link MediaPlayerInterface#UNKNOWN_TIME}.
    454      */
    455     @Override
    456     public long getBufferedPosition() {
    457         return mImpl.getBufferedPosition();
    458     }
    459 
    460     /**
    461      * Gets the current buffering state of the player.
    462      * During buffering, see {@link #getBufferedPosition()} for the quantifying the amount already
    463      * buffered.
    464      *
    465      * @return the buffering state.
    466      */
    467     @Override
    468     public @BuffState int getBufferingState() {
    469         return mImpl.getBufferingState();
    470     }
    471 
    472     /**
    473      * Get the playback speed.
    474      *
    475      * @return speed
    476      */
    477     @Override
    478     public float getPlaybackSpeed() {
    479         return mImpl.getPlaybackSpeed();
    480     }
    481 
    482     /**
    483      * Set the playback speed.
    484      */
    485     @Override
    486     public void setPlaybackSpeed(float speed) {
    487         mImpl.setPlaybackSpeed(speed);
    488     }
    489 
    490     /**
    491      * Sets the data source missing helper. Helper will be used to provide default implementation of
    492      * {@link MediaPlaylistAgent} when it isn't set by developer.
    493      * <p>
    494      * Default implementation of the {@link MediaPlaylistAgent} will call helper when a
    495      * {@link MediaItem2} in the playlist doesn't have a {@link DataSourceDesc}. This may happen
    496      * when
    497      * <ul>
    498      *      <li>{@link MediaItem2} specified by {@link #setPlaylist(List, MediaMetadata2)} doesn't
    499      *          have {@link DataSourceDesc}</li>
    500      *      <li>{@link MediaController2#addPlaylistItem(int, MediaItem2)} is called and accepted
    501      *          by {@link SessionCallback#onCommandRequest(
    502      *          MediaSession2, ControllerInfo, SessionCommand2)}.
    503      *          In that case, an item would be added automatically without the data source.</li>
    504      * </ul>
    505      * <p>
    506      * If it's not set, playback wouldn't happen for the item without data source descriptor.
    507      * <p>
    508      * The helper will be run on the executor that was specified by
    509      * {@link Builder#setSessionCallback(Executor, SessionCallback)}.
    510      *
    511      * @param helper a data source missing helper.
    512      * @throws IllegalStateException when the helper is set when the playlist agent is set
    513      * @see #setPlaylist(List, MediaMetadata2)
    514      * @see SessionCallback#onCommandRequest(MediaSession2, ControllerInfo, SessionCommand2)
    515      * @see SessionCommand2#COMMAND_CODE_PLAYLIST_ADD_ITEM
    516      * @see SessionCommand2#COMMAND_CODE_PLAYLIST_REPLACE_ITEM
    517      */
    518     @Override
    519     public void setOnDataSourceMissingHelper(@NonNull OnDataSourceMissingHelper helper) {
    520         mImpl.setOnDataSourceMissingHelper(helper);
    521     }
    522 
    523     /**
    524      * Clears the data source missing helper.
    525      *
    526      * @see #setOnDataSourceMissingHelper(OnDataSourceMissingHelper)
    527      */
    528     @Override
    529     public void clearOnDataSourceMissingHelper() {
    530         mImpl.clearOnDataSourceMissingHelper();
    531     }
    532 
    533     /**
    534      * Returns the playlist from the {@link MediaPlaylistAgent}.
    535      * <p>
    536      * This list may differ with the list that was specified with
    537      * {@link #setPlaylist(List, MediaMetadata2)} depending on the {@link MediaPlaylistAgent}
    538      * implementation. Use media items returned here for other playlist agent APIs such as
    539      * {@link MediaPlaylistAgent#skipToPlaylistItem(MediaItem2)}.
    540      *
    541      * @return playlist
    542      * @see MediaPlaylistAgent#getPlaylist()
    543      * @see SessionCallback#onPlaylistChanged(
    544      *          MediaSession2, MediaPlaylistAgent, List, MediaMetadata2)
    545      */
    546     @Override
    547     public List<MediaItem2> getPlaylist() {
    548         return mImpl.getPlaylist();
    549     }
    550 
    551     /**
    552      * Sets a list of {@link MediaItem2} to the {@link MediaPlaylistAgent}. Ensure uniqueness of
    553      * each {@link MediaItem2} in the playlist so the session can uniquely identity individual
    554      * items.
    555      * <p>
    556      * This may be an asynchronous call, and {@link MediaPlaylistAgent} may keep the copy of the
    557      * list. Wait for {@link SessionCallback#onPlaylistChanged(MediaSession2, MediaPlaylistAgent,
    558      * List, MediaMetadata2)} to know the operation finishes.
    559      * <p>
    560      * You may specify a {@link MediaItem2} without {@link DataSourceDesc}. In that case,
    561      * {@link MediaPlaylistAgent} has responsibility to dynamically query {link DataSourceDesc}
    562      * when such media item is ready for preparation or play. Default implementation needs
    563      * {@link OnDataSourceMissingHelper} for such case.
    564      * <p>
    565      * It's recommended to fill {@link MediaMetadata2} in each {@link MediaItem2} especially for the
    566      * duration information with the key {@link MediaMetadata2#METADATA_KEY_DURATION}. Without the
    567      * duration information in the metadata, session will do extra work to get the duration and send
    568      * it to the controller.
    569      *
    570      * @param list A list of {@link MediaItem2} objects to set as a play list.
    571      * @throws IllegalArgumentException if given list is {@code null}, or has duplicated media
    572      * items.
    573      * @see MediaPlaylistAgent#setPlaylist(List, MediaMetadata2)
    574      * @see SessionCallback#onPlaylistChanged(
    575      *          MediaSession2, MediaPlaylistAgent, List, MediaMetadata2)
    576      * @see #setOnDataSourceMissingHelper
    577      */
    578     @Override
    579     public void setPlaylist(@NonNull List<MediaItem2> list, @Nullable MediaMetadata2 metadata) {
    580         mImpl.setPlaylist(list, metadata);
    581     }
    582 
    583     /**
    584      * Skips to the item in the playlist.
    585      * <p>
    586      * This calls {@link MediaPlaylistAgent#skipToPlaylistItem(MediaItem2)} and the behavior depends
    587      * on the playlist agent implementation, especially with the shuffle/repeat mode.
    588      *
    589      * @param item The item in the playlist you want to play
    590      * @see #getShuffleMode()
    591      * @see #getRepeatMode()
    592      */
    593     @Override
    594     public void skipToPlaylistItem(@NonNull MediaItem2 item) {
    595         mImpl.skipToPlaylistItem(item);
    596     }
    597 
    598     /**
    599      * Skips to the previous item.
    600      * <p>
    601      * This calls {@link MediaPlaylistAgent#skipToPreviousItem()} and the behavior depends on the
    602      * playlist agent implementation, especially with the shuffle/repeat mode.
    603      *
    604      * @see #getShuffleMode()
    605      * @see #getRepeatMode()
    606      **/
    607     @Override
    608     public void skipToPreviousItem() {
    609         mImpl.skipToPreviousItem();
    610     }
    611 
    612     /**
    613      * Skips to the next item.
    614      * <p>
    615      * This calls {@link MediaPlaylistAgent#skipToNextItem()} and the behavior depends on the
    616      * playlist agent implementation, especially with the shuffle/repeat mode.
    617      *
    618      * @see #getShuffleMode()
    619      * @see #getRepeatMode()
    620      */
    621     @Override
    622     public void skipToNextItem() {
    623         mImpl.skipToNextItem();
    624     }
    625 
    626     /**
    627      * Gets the playlist metadata from the {@link MediaPlaylistAgent}.
    628      *
    629      * @return the playlist metadata
    630      */
    631     @Override
    632     public MediaMetadata2 getPlaylistMetadata() {
    633         return mImpl.getPlaylistMetadata();
    634     }
    635 
    636     /**
    637      * Adds the media item to the playlist at position index. Index equals or greater than
    638      * the current playlist size (e.g. {@link Integer#MAX_VALUE}) will add the item at the end of
    639      * the playlist.
    640      * <p>
    641      * This will not change the currently playing media item.
    642      * If index is less than or equal to the current index of the play list,
    643      * the current index of the play list will be incremented correspondingly.
    644      *
    645      * @param index the index you want to add
    646      * @param item the media item you want to add
    647      */
    648     @Override
    649     public void addPlaylistItem(int index, @NonNull MediaItem2 item) {
    650         mImpl.addPlaylistItem(index, item);
    651     }
    652 
    653     /**
    654      * Removes the media item in the playlist.
    655      * <p>
    656      * If the item is the currently playing item of the playlist, current playback
    657      * will be stopped and playback moves to next source in the list.
    658      *
    659      * @param item the media item you want to add
    660      */
    661     @Override
    662     public void removePlaylistItem(@NonNull MediaItem2 item) {
    663         mImpl.removePlaylistItem(item);
    664     }
    665 
    666     /**
    667      * Replaces the media item at index in the playlist. This can be also used to update metadata of
    668      * an item.
    669      *
    670      * @param index the index of the item to replace
    671      * @param item the new item
    672      */
    673     @Override
    674     public void replacePlaylistItem(int index, @NonNull MediaItem2 item) {
    675         mImpl.replacePlaylistItem(index, item);
    676     }
    677 
    678     /**
    679      * Return currently playing media item.
    680      *
    681      * @return currently playing media item
    682      */
    683     @Override
    684     public MediaItem2 getCurrentMediaItem() {
    685         return mImpl.getCurrentMediaItem();
    686     }
    687 
    688     /**
    689      * Updates the playlist metadata to the {@link MediaPlaylistAgent}.
    690      *
    691      * @param metadata metadata of the playlist
    692      */
    693     @Override
    694     public void updatePlaylistMetadata(@Nullable MediaMetadata2 metadata) {
    695         mImpl.updatePlaylistMetadata(metadata);
    696     }
    697 
    698     /**
    699      * Gets the repeat mode from the {@link MediaPlaylistAgent}.
    700      *
    701      * @return repeat mode
    702      * @see MediaPlaylistAgent#REPEAT_MODE_NONE
    703      * @see MediaPlaylistAgent#REPEAT_MODE_ONE
    704      * @see MediaPlaylistAgent#REPEAT_MODE_ALL
    705      * @see MediaPlaylistAgent#REPEAT_MODE_GROUP
    706      */
    707     @Override
    708     public @RepeatMode int getRepeatMode() {
    709         return mImpl.getRepeatMode();
    710     }
    711 
    712     /**
    713      * Sets the repeat mode to the {@link MediaPlaylistAgent}.
    714      *
    715      * @param repeatMode repeat mode
    716      * @see MediaPlaylistAgent#REPEAT_MODE_NONE
    717      * @see MediaPlaylistAgent#REPEAT_MODE_ONE
    718      * @see MediaPlaylistAgent#REPEAT_MODE_ALL
    719      * @see MediaPlaylistAgent#REPEAT_MODE_GROUP
    720      */
    721     @Override
    722     public void setRepeatMode(@RepeatMode int repeatMode) {
    723         mImpl.setRepeatMode(repeatMode);
    724     }
    725 
    726     /**
    727      * Gets the shuffle mode from the {@link MediaPlaylistAgent}.
    728      *
    729      * @return The shuffle mode
    730      * @see MediaPlaylistAgent#SHUFFLE_MODE_NONE
    731      * @see MediaPlaylistAgent#SHUFFLE_MODE_ALL
    732      * @see MediaPlaylistAgent#SHUFFLE_MODE_GROUP
    733      */
    734     @Override
    735     public @ShuffleMode int getShuffleMode() {
    736         return mImpl.getShuffleMode();
    737     }
    738 
    739     /**
    740      * Sets the shuffle mode to the {@link MediaPlaylistAgent}.
    741      *
    742      * @param shuffleMode The shuffle mode
    743      * @see MediaPlaylistAgent#SHUFFLE_MODE_NONE
    744      * @see MediaPlaylistAgent#SHUFFLE_MODE_ALL
    745      * @see MediaPlaylistAgent#SHUFFLE_MODE_GROUP
    746      */
    747     @Override
    748     public void setShuffleMode(@ShuffleMode int shuffleMode) {
    749         mImpl.setShuffleMode(shuffleMode);
    750     }
    751 
    752     /**
    753      * Interface definition of a callback to be invoked when a {@link MediaItem2} in the playlist
    754      * didn't have a {@link DataSourceDesc} but it's needed now for preparing or playing it.
    755      *
    756      * #see #setOnDataSourceMissingHelper
    757      */
    758     public interface OnDataSourceMissingHelper {
    759         /**
    760          * Called when a {@link MediaItem2} in the playlist didn't have a {@link DataSourceDesc}
    761          * but it's needed now for preparing or playing it. Returned data source descriptor will be
    762          * sent to the player directly to prepare or play the contents.
    763          * <p>
    764          * An exception may be thrown if the returned {@link DataSourceDesc} is duplicated in the
    765          * playlist, so items cannot be differentiated.
    766          *
    767          * @param session the session for this event
    768          * @param item media item from the controller
    769          * @return a data source descriptor if the media item. Can be {@code null} if the content
    770          *        isn't available.
    771          */
    772         @Nullable DataSourceDesc onDataSourceMissing(@NonNull MediaSession2 session,
    773                 @NonNull MediaItem2 item);
    774     }
    775 
    776     /**
    777      * Callback to be called for all incoming commands from {@link MediaController2}s.
    778      * <p>
    779      * If it's not set, the session will accept all controllers and all incoming commands by
    780      * default.
    781      */
    782     public abstract static class SessionCallback {
    783         /**
    784          * Called when a controller is created for this session. Return allowed commands for
    785          * controller. By default it allows all connection requests and commands.
    786          * <p>
    787          * You can reject the connection by return {@code null}. In that case, controller receives
    788          * {@link MediaController2.ControllerCallback#onDisconnected(MediaController2)} and cannot
    789          * be usable.
    790          *
    791          * @param session the session for this event
    792          * @param controller controller information.
    793          * @return allowed commands. Can be {@code null} to reject connection.
    794          */
    795         public @Nullable SessionCommandGroup2 onConnect(@NonNull MediaSession2 session,
    796                 @NonNull ControllerInfo controller) {
    797             SessionCommandGroup2 commands = new SessionCommandGroup2();
    798             commands.addAllPredefinedCommands();
    799             return commands;
    800         }
    801 
    802         /**
    803          * Called when a controller is disconnected
    804          *
    805          * @param session the session for this event
    806          * @param controller controller information
    807          */
    808         public void onDisconnected(@NonNull MediaSession2 session,
    809                 @NonNull ControllerInfo controller) { }
    810 
    811         /**
    812          * Called when a controller sent a command which will be sent directly to one of the
    813          * following:
    814          * <ul>
    815          *  <li> {@link MediaPlayerInterface} </li>
    816          *  <li> {@link MediaPlaylistAgent} </li>
    817          *  <li> {@link android.media.AudioManager} or {@link VolumeProviderCompat} </li>
    818          * </ul>
    819          * Return {@code false} here to reject the request and stop sending command.
    820          *
    821          * @param session the session for this event
    822          * @param controller controller information.
    823          * @param command a command. This method will be called for every single command.
    824          * @return {@code true} if you want to accept incoming command. {@code false} otherwise.
    825          * @see SessionCommand2#COMMAND_CODE_PLAYBACK_PLAY
    826          * @see SessionCommand2#COMMAND_CODE_PLAYBACK_PAUSE
    827          * @see SessionCommand2#COMMAND_CODE_PLAYBACK_RESET
    828          * @see SessionCommand2#COMMAND_CODE_PLAYLIST_SKIP_TO_NEXT_ITEM
    829          * @see SessionCommand2#COMMAND_CODE_PLAYLIST_SKIP_TO_PREV_ITEM
    830          * @see SessionCommand2#COMMAND_CODE_PLAYBACK_PREPARE
    831          * @see SessionCommand2#COMMAND_CODE_PLAYBACK_SEEK_TO
    832          * @see SessionCommand2#COMMAND_CODE_PLAYLIST_SKIP_TO_PLAYLIST_ITEM
    833          * @see SessionCommand2#COMMAND_CODE_PLAYLIST_SET_SHUFFLE_MODE
    834          * @see SessionCommand2#COMMAND_CODE_PLAYLIST_SET_REPEAT_MODE
    835          * @see SessionCommand2#COMMAND_CODE_PLAYLIST_ADD_ITEM
    836          * @see SessionCommand2#COMMAND_CODE_PLAYLIST_REMOVE_ITEM
    837          * @see SessionCommand2#COMMAND_CODE_PLAYLIST_REPLACE_ITEM
    838          * @see SessionCommand2#COMMAND_CODE_PLAYLIST_GET_LIST
    839          * @see SessionCommand2#COMMAND_CODE_PLAYLIST_SET_LIST
    840          * @see SessionCommand2#COMMAND_CODE_PLAYLIST_GET_LIST_METADATA
    841          * @see SessionCommand2#COMMAND_CODE_PLAYLIST_SET_LIST_METADATA
    842          * @see SessionCommand2#COMMAND_CODE_VOLUME_SET_VOLUME
    843          * @see SessionCommand2#COMMAND_CODE_VOLUME_ADJUST_VOLUME
    844          */
    845         public boolean onCommandRequest(@NonNull MediaSession2 session,
    846                 @NonNull ControllerInfo controller, @NonNull SessionCommand2 command) {
    847             return true;
    848         }
    849 
    850         /**
    851          * Called when a controller set rating of a media item through
    852          * {@link MediaController2#setRating(String, Rating2)}.
    853          * <p>
    854          * To allow setting user rating for a {@link MediaItem2}, the media item's metadata
    855          * should have {@link Rating2} with the key {@link MediaMetadata2#METADATA_KEY_USER_RATING},
    856          * in order to provide possible rating style for controller. Controller will follow the
    857          * rating style.
    858          *
    859          * @param session the session for this event
    860          * @param controller controller information
    861          * @param mediaId media id from the controller
    862          * @param rating new rating from the controller
    863          * @see SessionCommand2#COMMAND_CODE_SESSION_SET_RATING
    864          */
    865         public void onSetRating(@NonNull MediaSession2 session, @NonNull ControllerInfo controller,
    866                 @NonNull String mediaId, @NonNull Rating2 rating) { }
    867 
    868         /**
    869          * Called when a controller sent a custom command through
    870          * {@link MediaController2#sendCustomCommand(SessionCommand2, Bundle, ResultReceiver)}.
    871          *
    872          * @param session the session for this event
    873          * @param controller controller information
    874          * @param customCommand custom command.
    875          * @param args optional arguments
    876          * @param cb optional result receiver
    877          * @see SessionCommand2#COMMAND_CODE_CUSTOM
    878          */
    879         public void onCustomCommand(@NonNull MediaSession2 session,
    880                 @NonNull ControllerInfo controller, @NonNull SessionCommand2 customCommand,
    881                 @Nullable Bundle args, @Nullable ResultReceiver cb) { }
    882 
    883         /**
    884          * Called when a controller requested to play a specific mediaId through
    885          * {@link MediaController2#playFromMediaId(String, Bundle)}.
    886          *
    887          * @param session the session for this event
    888          * @param controller controller information
    889          * @param mediaId media id
    890          * @param extras optional extra bundle
    891          * @see SessionCommand2#COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID
    892          */
    893         public void onPlayFromMediaId(@NonNull MediaSession2 session,
    894                 @NonNull ControllerInfo controller, @NonNull String mediaId,
    895                 @Nullable Bundle extras) { }
    896 
    897         /**
    898          * Called when a controller requested to begin playback from a search query through
    899          * {@link MediaController2#playFromSearch(String, Bundle)}
    900          * <p>
    901          * An empty query indicates that the app may play any music. The implementation should
    902          * attempt to make a smart choice about what to play.
    903          *
    904          * @param session the session for this event
    905          * @param controller controller information
    906          * @param query query string. Can be empty to indicate any suggested media
    907          * @param extras optional extra bundle
    908          * @see SessionCommand2#COMMAND_CODE_SESSION_PLAY_FROM_SEARCH
    909          */
    910         public void onPlayFromSearch(@NonNull MediaSession2 session,
    911                 @NonNull ControllerInfo controller, @NonNull String query,
    912                 @Nullable Bundle extras) { }
    913 
    914         /**
    915          * Called when a controller requested to play a specific media item represented by a URI
    916          * through {@link MediaController2#playFromUri(Uri, Bundle)}
    917          *
    918          * @param session the session for this event
    919          * @param controller controller information
    920          * @param uri uri
    921          * @param extras optional extra bundle
    922          * @see SessionCommand2#COMMAND_CODE_SESSION_PLAY_FROM_URI
    923          */
    924         public void onPlayFromUri(@NonNull MediaSession2 session,
    925                 @NonNull ControllerInfo controller, @NonNull Uri uri,
    926                 @Nullable Bundle extras) { }
    927 
    928         /**
    929          * Called when a controller requested to prepare for playing a specific mediaId through
    930          * {@link MediaController2#prepareFromMediaId(String, Bundle)}.
    931          * <p>
    932          * During the preparation, a session should not hold audio focus in order to allow other
    933          * sessions play seamlessly. The state of playback should be updated to
    934          * {@link MediaPlayerInterface#PLAYER_STATE_PAUSED} after the preparation is done.
    935          * <p>
    936          * The playback of the prepared content should start in the later calls of
    937          * {@link MediaSession2#play()}.
    938          * <p>
    939          * Override {@link #onPlayFromMediaId} to handle requests for starting
    940          * playback without preparation.
    941          *
    942          * @param session the session for this event
    943          * @param controller controller information
    944          * @param mediaId media id to prepare
    945          * @param extras optional extra bundle
    946          * @see SessionCommand2#COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID
    947          */
    948         public void onPrepareFromMediaId(@NonNull MediaSession2 session,
    949                 @NonNull ControllerInfo controller, @NonNull String mediaId,
    950                 @Nullable Bundle extras) { }
    951 
    952         /**
    953          * Called when a controller requested to prepare playback from a search query through
    954          * {@link MediaController2#prepareFromSearch(String, Bundle)}.
    955          * <p>
    956          * An empty query indicates that the app may prepare any music. The implementation should
    957          * attempt to make a smart choice about what to play.
    958          * <p>
    959          * The state of playback should be updated to
    960          * {@link MediaPlayerInterface#PLAYER_STATE_PAUSED} after the preparation is done.
    961          * The playback of the prepared content should start in the
    962          * later calls of {@link MediaSession2#play()}.
    963          * <p>
    964          * Override {@link #onPlayFromSearch} to handle requests for starting playback without
    965          * preparation.
    966          *
    967          * @param session the session for this event
    968          * @param controller controller information
    969          * @param query query string. Can be empty to indicate any suggested media
    970          * @param extras optional extra bundle
    971          * @see SessionCommand2#COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH
    972          */
    973         public void onPrepareFromSearch(@NonNull MediaSession2 session,
    974                 @NonNull ControllerInfo controller, @NonNull String query,
    975                 @Nullable Bundle extras) { }
    976 
    977         /**
    978          * Called when a controller requested to prepare a specific media item represented by a URI
    979          * through {@link MediaController2#prepareFromUri(Uri, Bundle)}.
    980          * <p>
    981          * During the preparation, a session should not hold audio focus in order to allow
    982          * other sessions play seamlessly. The state of playback should be updated to
    983          * {@link MediaPlayerInterface#PLAYER_STATE_PAUSED} after the preparation is done.
    984          * <p>
    985          * The playback of the prepared content should start in the later calls of
    986          * {@link MediaSession2#play()}.
    987          * <p>
    988          * Override {@link #onPlayFromUri} to handle requests for starting playback without
    989          * preparation.
    990          *
    991          * @param session the session for this event
    992          * @param controller controller information
    993          * @param uri uri
    994          * @param extras optional extra bundle
    995          * @see SessionCommand2#COMMAND_CODE_SESSION_PREPARE_FROM_URI
    996          */
    997         public void onPrepareFromUri(@NonNull MediaSession2 session,
    998                 @NonNull ControllerInfo controller, @NonNull Uri uri, @Nullable Bundle extras) { }
    999 
   1000         /**
   1001          * Called when a controller called {@link MediaController2#fastForward()}
   1002          *
   1003          * @param session the session for this event
   1004          * @param controller controller information
   1005          * @see SessionCommand2#COMMAND_CODE_SESSION_FAST_FORWARD
   1006          */
   1007         public void onFastForward(@NonNull MediaSession2 session, ControllerInfo controller) { }
   1008 
   1009         /**
   1010          * Called when a controller called {@link MediaController2#rewind()}
   1011          *
   1012          * @param session the session for this event
   1013          * @param controller controller information
   1014          * @see SessionCommand2#COMMAND_CODE_SESSION_REWIND
   1015          */
   1016         public void onRewind(@NonNull MediaSession2 session, ControllerInfo controller) { }
   1017 
   1018         /**
   1019          * Called when a controller called {@link MediaController2#subscribeRoutesInfo()}
   1020          * Session app should notify the routes information by calling
   1021          * {@link MediaSession2#notifyRoutesInfoChanged(ControllerInfo, List)}.
   1022          *
   1023          * @param session the session for this event
   1024          * @param controller controller information
   1025          * @see SessionCommand2#COMMAND_CODE_SESSION_SUBSCRIBE_ROUTES_INFO
   1026          */
   1027         public void onSubscribeRoutesInfo(@NonNull MediaSession2 session,
   1028                 @NonNull ControllerInfo controller) { }
   1029 
   1030         /**
   1031          * Called when a controller called {@link MediaController2#unsubscribeRoutesInfo()}
   1032          *
   1033          * @param session the session for this event
   1034          * @param controller controller information
   1035          * @see SessionCommand2#COMMAND_CODE_SESSION_UNSUBSCRIBE_ROUTES_INFO
   1036          */
   1037         public void onUnsubscribeRoutesInfo(@NonNull MediaSession2 session,
   1038                 @NonNull ControllerInfo controller) { }
   1039 
   1040         /**
   1041          * Called when a controller called {@link MediaController2#selectRoute(Bundle)}.
   1042          * @param session the session for this event
   1043          * @param controller controller information
   1044          * @param route The route bundle which may be from MediaRouteDescritor.asBundle().
   1045          * @see SessionCommand2#COMMAND_CODE_SESSION_SELECT_ROUTE
   1046          */
   1047         public void onSelectRoute(@NonNull MediaSession2 session,
   1048                 @NonNull ControllerInfo controller, @NonNull Bundle route) { }
   1049         /**
   1050          * Called when the player's current playing item is changed
   1051          * <p>
   1052          * When it's called, you should invalidate previous playback information and wait for later
   1053          * callbacks.
   1054          *
   1055          * @param session the controller for this event
   1056          * @param player the player for this event
   1057          * @param item new item
   1058          */
   1059         public void onCurrentMediaItemChanged(@NonNull MediaSession2 session,
   1060                 @NonNull MediaPlayerInterface player, @Nullable MediaItem2 item) { }
   1061 
   1062         /**
   1063          * Called when the player is <i>prepared</i>, i.e. it is ready to play the content
   1064          * referenced by the given data source.
   1065          * @param session the session for this event
   1066          * @param player the player for this event
   1067          * @param item the media item for which buffering is happening
   1068          */
   1069         public void onMediaPrepared(@NonNull MediaSession2 session,
   1070                 @NonNull MediaPlayerInterface player, @NonNull MediaItem2 item) { }
   1071 
   1072         /**
   1073          * Called to indicate that the state of the player has changed.
   1074          * See {@link MediaPlayerInterface#getPlayerState()} for polling the player state.
   1075          * @param session the session for this event
   1076          * @param player the player for this event
   1077          * @param state the new state of the player.
   1078          */
   1079         public void onPlayerStateChanged(@NonNull MediaSession2 session,
   1080                 @NonNull MediaPlayerInterface player, @PlayerState int state) { }
   1081 
   1082         /**
   1083          * Called to report buffering events for a data source.
   1084          *
   1085          * @param session the session for this event
   1086          * @param player the player for this event
   1087          * @param item the media item for which buffering is happening.
   1088          * @param state the new buffering state.
   1089          */
   1090         public void onBufferingStateChanged(@NonNull MediaSession2 session,
   1091                 @NonNull MediaPlayerInterface player, @NonNull MediaItem2 item,
   1092                 @BuffState int state) { }
   1093 
   1094         /**
   1095          * Called to indicate that the playback speed has changed.
   1096          * @param session the session for this event
   1097          * @param player the player for this event
   1098          * @param speed the new playback speed.
   1099          */
   1100         public void onPlaybackSpeedChanged(@NonNull MediaSession2 session,
   1101                 @NonNull MediaPlayerInterface player, float speed) { }
   1102 
   1103         /**
   1104          * Called to indicate that {@link #seekTo(long)} is completed.
   1105          *
   1106          * @param session the session for this event.
   1107          * @param player the player that has completed seeking.
   1108          * @param position the previous seeking request.
   1109          * @see #seekTo(long)
   1110          */
   1111         public void onSeekCompleted(@NonNull MediaSession2 session,
   1112                 @NonNull MediaPlayerInterface player, long position) { }
   1113 
   1114         /**
   1115          * Called when a playlist is changed from the {@link MediaPlaylistAgent}.
   1116          * <p>
   1117          * This is called when the underlying agent has called
   1118          * {@link PlaylistEventCallback#onPlaylistChanged(MediaPlaylistAgent,
   1119          * List, MediaMetadata2)}.
   1120          *
   1121          * @param session the session for this event
   1122          * @param playlistAgent playlist agent for this event
   1123          * @param list new playlist
   1124          * @param metadata new metadata
   1125          */
   1126         public void onPlaylistChanged(@NonNull MediaSession2 session,
   1127                 @NonNull MediaPlaylistAgent playlistAgent, @NonNull List<MediaItem2> list,
   1128                 @Nullable MediaMetadata2 metadata) { }
   1129 
   1130         /**
   1131          * Called when a playlist metadata is changed.
   1132          *
   1133          * @param session the session for this event
   1134          * @param playlistAgent playlist agent for this event
   1135          * @param metadata new metadata
   1136          */
   1137         public void onPlaylistMetadataChanged(@NonNull MediaSession2 session,
   1138                 @NonNull MediaPlaylistAgent playlistAgent, @Nullable MediaMetadata2 metadata) { }
   1139 
   1140         /**
   1141          * Called when the shuffle mode is changed.
   1142          *
   1143          * @param session the session for this event
   1144          * @param playlistAgent playlist agent for this event
   1145          * @param shuffleMode repeat mode
   1146          * @see MediaPlaylistAgent#SHUFFLE_MODE_NONE
   1147          * @see MediaPlaylistAgent#SHUFFLE_MODE_ALL
   1148          * @see MediaPlaylistAgent#SHUFFLE_MODE_GROUP
   1149          */
   1150         public void onShuffleModeChanged(@NonNull MediaSession2 session,
   1151                 @NonNull MediaPlaylistAgent playlistAgent,
   1152                 @MediaPlaylistAgent.ShuffleMode int shuffleMode) { }
   1153 
   1154         /**
   1155          * Called when the repeat mode is changed.
   1156          *
   1157          * @param session the session for this event
   1158          * @param playlistAgent playlist agent for this event
   1159          * @param repeatMode repeat mode
   1160          * @see MediaPlaylistAgent#REPEAT_MODE_NONE
   1161          * @see MediaPlaylistAgent#REPEAT_MODE_ONE
   1162          * @see MediaPlaylistAgent#REPEAT_MODE_ALL
   1163          * @see MediaPlaylistAgent#REPEAT_MODE_GROUP
   1164          */
   1165         public void onRepeatModeChanged(@NonNull MediaSession2 session,
   1166                 @NonNull MediaPlaylistAgent playlistAgent,
   1167                 @MediaPlaylistAgent.RepeatMode int repeatMode) { }
   1168     }
   1169 
   1170     /**
   1171      * Builder for {@link MediaSession2}.
   1172      * <p>
   1173      * Any incoming event from the {@link MediaController2} will be handled on the thread
   1174      * that created session with the {@link Builder#build()}.
   1175      */
   1176     public static final class Builder extends BuilderBase<MediaSession2, Builder, SessionCallback> {
   1177         private MediaSession2ImplBase.Builder mImpl;
   1178 
   1179         public Builder(Context context) {
   1180             super(context);
   1181             mImpl = new MediaSession2ImplBase.Builder(context);
   1182             setImpl(mImpl);
   1183         }
   1184 
   1185         @Override
   1186         public @NonNull Builder setPlayer(@NonNull MediaPlayerInterface player) {
   1187             return super.setPlayer(player);
   1188         }
   1189 
   1190         @Override
   1191         public @NonNull Builder setPlaylistAgent(@NonNull MediaPlaylistAgent playlistAgent) {
   1192             return super.setPlaylistAgent(playlistAgent);
   1193         }
   1194 
   1195         @Override
   1196         public @NonNull Builder setVolumeProvider(@Nullable VolumeProviderCompat volumeProvider) {
   1197             return super.setVolumeProvider(volumeProvider);
   1198         }
   1199 
   1200         @Override
   1201         public @NonNull Builder setSessionActivity(@Nullable PendingIntent pi) {
   1202             return super.setSessionActivity(pi);
   1203         }
   1204 
   1205         @Override
   1206         public @NonNull Builder setId(@NonNull String id) {
   1207             return super.setId(id);
   1208         }
   1209 
   1210         @Override
   1211         public @NonNull Builder setSessionCallback(@NonNull Executor executor,
   1212                 @NonNull SessionCallback callback) {
   1213             return super.setSessionCallback(executor, callback);
   1214         }
   1215 
   1216         @Override
   1217         public @NonNull MediaSession2 build() {
   1218             return super.build();
   1219         }
   1220     }
   1221 
   1222     /**
   1223      * Information of a controller.
   1224      */
   1225     public static final class ControllerInfo {
   1226         private final int mUid;
   1227         private final String mPackageName;
   1228         private final boolean mIsTrusted;
   1229         private final ControllerCb mControllerCb;
   1230 
   1231         /**
   1232          * @hide
   1233          */
   1234         @RestrictTo(LIBRARY_GROUP)
   1235         ControllerInfo(@NonNull String packageName, int pid, int uid, @NonNull ControllerCb cb) {
   1236             mUid = uid;
   1237             mPackageName = packageName;
   1238             mIsTrusted = false;
   1239             mControllerCb = cb;
   1240         }
   1241 
   1242         /**
   1243          * @return package name of the controller
   1244          */
   1245         public @NonNull String getPackageName() {
   1246             return mPackageName;
   1247         }
   1248 
   1249         /**
   1250          * @return uid of the controller
   1251          */
   1252         public int getUid() {
   1253             return mUid;
   1254         }
   1255 
   1256         /**
   1257          * Return if the controller has granted {@code android.permission.MEDIA_CONTENT_CONTROL} or
   1258          * has a enabled notification listener so can be trusted to accept connection and incoming
   1259          * command request.
   1260          *
   1261          * @return {@code true} if the controller is trusted.
   1262          * @hide
   1263          */
   1264         @RestrictTo(LIBRARY_GROUP)
   1265         public boolean isTrusted() {
   1266             return mIsTrusted;
   1267         }
   1268 
   1269         @Override
   1270         public int hashCode() {
   1271             return mControllerCb.hashCode();
   1272         }
   1273 
   1274         @Override
   1275         public boolean equals(Object obj) {
   1276             if (!(obj instanceof ControllerInfo)) {
   1277                 return false;
   1278             }
   1279             ControllerInfo other = (ControllerInfo) obj;
   1280             return mControllerCb.equals(other.mControllerCb);
   1281         }
   1282 
   1283         @Override
   1284         public String toString() {
   1285             return "ControllerInfo {pkg=" + mPackageName + ", uid=" + mUid + "})";
   1286         }
   1287 
   1288         @NonNull IBinder getId() {
   1289             return mControllerCb.getId();
   1290         }
   1291 
   1292         @NonNull ControllerCb getControllerCb() {
   1293             return mControllerCb;
   1294         }
   1295     }
   1296 
   1297     /**
   1298      * Button for a {@link SessionCommand2} that will be shown by the controller.
   1299      * <p>
   1300      * It's up to the controller's decision to respect or ignore this customization request.
   1301      */
   1302     public static final class CommandButton {
   1303         private static final String KEY_COMMAND =
   1304                 "android.media.media_session2.command_button.command";
   1305         private static final String KEY_ICON_RES_ID =
   1306                 "android.media.media_session2.command_button.icon_res_id";
   1307         private static final String KEY_DISPLAY_NAME =
   1308                 "android.media.media_session2.command_button.display_name";
   1309         private static final String KEY_EXTRAS =
   1310                 "android.media.media_session2.command_button.extras";
   1311         private static final String KEY_ENABLED =
   1312                 "android.media.media_session2.command_button.enabled";
   1313 
   1314         private SessionCommand2 mCommand;
   1315         private int mIconResId;
   1316         private String mDisplayName;
   1317         private Bundle mExtras;
   1318         private boolean mEnabled;
   1319 
   1320         private CommandButton(@Nullable SessionCommand2 command, int iconResId,
   1321                 @Nullable String displayName, Bundle extras, boolean enabled) {
   1322             mCommand = command;
   1323             mIconResId = iconResId;
   1324             mDisplayName = displayName;
   1325             mExtras = extras;
   1326             mEnabled = enabled;
   1327         }
   1328 
   1329         /**
   1330          * Get command associated with this button. Can be {@code null} if the button isn't enabled
   1331          * and only providing placeholder.
   1332          *
   1333          * @return command or {@code null}
   1334          */
   1335         public @Nullable SessionCommand2 getCommand() {
   1336             return mCommand;
   1337         }
   1338 
   1339         /**
   1340          * Resource id of the button in this package. Can be {@code 0} if the command is predefined
   1341          * and custom icon isn't needed.
   1342          *
   1343          * @return resource id of the icon. Can be {@code 0}.
   1344          */
   1345         public int getIconResId() {
   1346             return mIconResId;
   1347         }
   1348 
   1349         /**
   1350          * Display name of the button. Can be {@code null} or empty if the command is predefined
   1351          * and custom name isn't needed.
   1352          *
   1353          * @return custom display name. Can be {@code null} or empty.
   1354          */
   1355         public @Nullable String getDisplayName() {
   1356             return mDisplayName;
   1357         }
   1358 
   1359         /**
   1360          * Extra information of the button. It's private information between session and controller.
   1361          *
   1362          * @return
   1363          */
   1364         public @Nullable Bundle getExtras() {
   1365             return mExtras;
   1366         }
   1367 
   1368         /**
   1369          * Return whether it's enabled.
   1370          *
   1371          * @return {@code true} if enabled. {@code false} otherwise.
   1372          */
   1373         public boolean isEnabled() {
   1374             return mEnabled;
   1375         }
   1376 
   1377         /**
   1378          * @hide
   1379          * @return Bundle
   1380          */
   1381         @RestrictTo(LIBRARY_GROUP)
   1382         public @NonNull Bundle toBundle() {
   1383             Bundle bundle = new Bundle();
   1384             bundle.putBundle(KEY_COMMAND, mCommand.toBundle());
   1385             bundle.putInt(KEY_ICON_RES_ID, mIconResId);
   1386             bundle.putString(KEY_DISPLAY_NAME, mDisplayName);
   1387             bundle.putBundle(KEY_EXTRAS, mExtras);
   1388             bundle.putBoolean(KEY_ENABLED, mEnabled);
   1389             return bundle;
   1390         }
   1391 
   1392         /**
   1393          * @hide
   1394          * @return CommandButton
   1395          */
   1396         @RestrictTo(LIBRARY_GROUP)
   1397         public static @Nullable CommandButton fromBundle(Bundle bundle) {
   1398             if (bundle == null) {
   1399                 return null;
   1400             }
   1401             CommandButton.Builder builder = new CommandButton.Builder();
   1402             builder.setCommand(SessionCommand2.fromBundle(bundle.getBundle(KEY_COMMAND)));
   1403             builder.setIconResId(bundle.getInt(KEY_ICON_RES_ID, 0));
   1404             builder.setDisplayName(bundle.getString(KEY_DISPLAY_NAME));
   1405             builder.setExtras(bundle.getBundle(KEY_EXTRAS));
   1406             builder.setEnabled(bundle.getBoolean(KEY_ENABLED));
   1407             try {
   1408                 return builder.build();
   1409             } catch (IllegalStateException e) {
   1410                 // Malformed or version mismatch. Return null for now.
   1411                 return null;
   1412             }
   1413         }
   1414 
   1415         /**
   1416          * Builder for {@link CommandButton}.
   1417          */
   1418         public static final class Builder {
   1419             private SessionCommand2 mCommand;
   1420             private int mIconResId;
   1421             private String mDisplayName;
   1422             private Bundle mExtras;
   1423             private boolean mEnabled;
   1424 
   1425             /**
   1426              * Sets the {@link SessionCommand2} that would be sent to the session when the button
   1427              * is clicked.
   1428              *
   1429              * @param command session command
   1430              */
   1431             public @NonNull Builder setCommand(@Nullable SessionCommand2 command) {
   1432                 mCommand = command;
   1433                 return this;
   1434             }
   1435 
   1436             /**
   1437              * Sets the bitmap-type (e.g. PNG) icon resource id of the button.
   1438              * <p>
   1439              * None bitmap type (e.g. VectorDrawabale) may cause unexpected behavior when it's sent
   1440              * to {@link MediaController2} app, so please avoid using it especially for the older
   1441              * platform (API < 21).
   1442              *
   1443              * @param resId resource id of the button
   1444              */
   1445             public @NonNull Builder setIconResId(int resId) {
   1446                 mIconResId = resId;
   1447                 return this;
   1448             }
   1449 
   1450             /**
   1451              * Sets the display name of the button.
   1452              *
   1453              * @param displayName display name of the button
   1454              */
   1455             public @NonNull Builder setDisplayName(@Nullable String displayName) {
   1456                 mDisplayName = displayName;
   1457                 return this;
   1458             }
   1459 
   1460             /**
   1461              * Sets whether the button is enabled. Can be {@code false} to indicate that the button
   1462              * should be shown but isn't clickable.
   1463              *
   1464              * @param enabled {@code true} if the button is enabled and ready.
   1465              *          {@code false} otherwise.
   1466              */
   1467             public @NonNull Builder setEnabled(boolean enabled) {
   1468                 mEnabled = enabled;
   1469                 return this;
   1470             }
   1471 
   1472             /**
   1473              * Sets the extras of the button.
   1474              *
   1475              * @param extras extras information of the button
   1476              */
   1477             public @NonNull Builder setExtras(@Nullable Bundle extras) {
   1478                 mExtras = extras;
   1479                 return this;
   1480             }
   1481 
   1482             /**
   1483              * Builds the {@link CommandButton}.
   1484              *
   1485              * @return a new {@link CommandButton}
   1486              */
   1487             public @NonNull CommandButton build() {
   1488                 return new CommandButton(mCommand, mIconResId, mDisplayName, mExtras, mEnabled);
   1489             }
   1490         }
   1491     }
   1492 
   1493     abstract static class ControllerCb {
   1494         @Override
   1495         public int hashCode() {
   1496             return getId().hashCode();
   1497         }
   1498 
   1499         @Override
   1500         public boolean equals(Object obj) {
   1501             if (!(obj instanceof ControllerCb)) {
   1502                 return false;
   1503             }
   1504             ControllerCb other = (ControllerCb) obj;
   1505             return getId().equals(other.getId());
   1506         }
   1507 
   1508         abstract @NonNull IBinder getId();
   1509 
   1510         // Mostly matched with the methods in MediaController2.ControllerCallback
   1511         abstract void onCustomLayoutChanged(@NonNull List<CommandButton> layout)
   1512                 throws RemoteException;
   1513         abstract void onPlaybackInfoChanged(@NonNull PlaybackInfo info) throws RemoteException;
   1514         abstract void onAllowedCommandsChanged(@NonNull SessionCommandGroup2 commands)
   1515                 throws RemoteException;
   1516         abstract void onCustomCommand(@NonNull SessionCommand2 command, @Nullable Bundle args,
   1517                 @Nullable ResultReceiver receiver) throws RemoteException;
   1518         abstract void onPlayerStateChanged(int playerState) throws RemoteException;
   1519         abstract void onPlaybackSpeedChanged(float speed) throws RemoteException;
   1520         abstract void onBufferingStateChanged(@NonNull MediaItem2 item,
   1521                 @MediaPlayerInterface.BuffState int state) throws RemoteException;
   1522         abstract void onSeekCompleted(long position) throws RemoteException;
   1523         abstract void onError(@ErrorCode int errorCode, @Nullable Bundle extras)
   1524                 throws RemoteException;
   1525         abstract void onCurrentMediaItemChanged(@Nullable MediaItem2 item) throws RemoteException;
   1526         abstract void onPlaylistChanged(@NonNull List<MediaItem2> playlist,
   1527                 @Nullable MediaMetadata2 metadata) throws RemoteException;
   1528         abstract void onPlaylistMetadataChanged(@Nullable MediaMetadata2 metadata)
   1529                 throws RemoteException;
   1530         abstract void onShuffleModeChanged(@MediaPlaylistAgent.ShuffleMode int shuffleMode)
   1531                 throws RemoteException;
   1532         abstract void onRepeatModeChanged(@MediaPlaylistAgent.RepeatMode int repeatMode)
   1533                 throws RemoteException;
   1534         abstract void onRoutesInfoChanged(@Nullable List<Bundle> routes) throws RemoteException;
   1535         abstract void onChildrenChanged(@NonNull  String parentId, int itemCount,
   1536                 @Nullable Bundle extras) throws RemoteException;
   1537         abstract void onSearchResultChanged(@NonNull String query, int itemCount,
   1538                 @Nullable Bundle extras) throws RemoteException;
   1539     }
   1540 
   1541     abstract static class SupportLibraryImpl extends MediaInterface2.SessionPlayer
   1542             implements AutoCloseable {
   1543         abstract void updatePlayer(@NonNull MediaPlayerInterface player,
   1544                 @Nullable MediaPlaylistAgent playlistAgent,
   1545                 @Nullable VolumeProviderCompat volumeProvider);
   1546         abstract @NonNull MediaPlayerInterface getPlayer();
   1547         abstract @NonNull MediaPlaylistAgent getPlaylistAgent();
   1548         abstract @Nullable VolumeProviderCompat getVolumeProvider();
   1549         abstract @NonNull SessionToken2 getToken();
   1550         abstract @NonNull List<ControllerInfo> getConnectedControllers();
   1551 
   1552         abstract void setAudioFocusRequest(@Nullable AudioFocusRequest afr);
   1553         abstract void setCustomLayout(@NonNull ControllerInfo controller,
   1554                 @NonNull List<CommandButton> layout);
   1555         abstract void setAllowedCommands(@NonNull ControllerInfo controller,
   1556                 @NonNull SessionCommandGroup2 commands);
   1557         abstract void sendCustomCommand(@NonNull SessionCommand2 command, @Nullable Bundle args);
   1558         abstract void sendCustomCommand(@NonNull ControllerInfo controller,
   1559                 @NonNull SessionCommand2 command, @Nullable Bundle args,
   1560                 @Nullable ResultReceiver receiver);
   1561         abstract void notifyRoutesInfoChanged(@NonNull ControllerInfo controller,
   1562                 @Nullable List<Bundle> routes);
   1563 
   1564         // LibrarySession methods
   1565         abstract void notifyChildrenChanged(@NonNull ControllerInfo controller,
   1566                 @NonNull String parentId, int itemCount, @Nullable Bundle extras,
   1567                 @NonNull List<MediaSessionManager.RemoteUserInfo> subscribingBrowsers);
   1568         abstract void notifySearchResultChanged(@NonNull ControllerInfo controller,
   1569                 @NonNull String query, int itemCount, @Nullable Bundle extras);
   1570 
   1571         // Internally used methods
   1572         abstract MediaSession2 createInstance();
   1573         abstract MediaSession2 getInstance();
   1574         abstract MediaSessionCompat getSessionCompat();
   1575         abstract Context getContext();
   1576         abstract Executor getCallbackExecutor();
   1577         abstract SessionCallback getCallback();
   1578         abstract boolean isClosed();
   1579         abstract PlaybackStateCompat getPlaybackStateCompat();
   1580         abstract PlaybackInfo getPlaybackInfo();
   1581     }
   1582 
   1583     /**
   1584      * Base builder class for MediaSession2 and its subclass. Any change in this class should be
   1585      * also applied to the subclasses {@link MediaSession2.Builder} and
   1586      * {@link MediaLibraryService2.MediaLibrarySession.Builder}.
   1587      * <p>
   1588      * APIs here should be package private, but should have documentations for developers.
   1589      * Otherwise, javadoc will generate documentation with the generic types such as follows.
   1590      * <pre>U extends BuilderBase<T, U, C> setSessionCallback(Executor executor, C callback)</pre>
   1591      * <p>
   1592      * This class is hidden to prevent from generating test stub, which fails with
   1593      * 'unexpected bound' because it tries to auto generate stub class as follows.
   1594      * <pre>abstract static class BuilderBase<
   1595      *      T extends android.media.MediaSession2,
   1596      *      U extends android.media.MediaSession2.BuilderBase<
   1597      *              T, U, C extends android.media.MediaSession2.SessionCallback>, C></pre>
   1598      * @hide
   1599      */
   1600     @RestrictTo(LIBRARY_GROUP)
   1601     abstract static class BuilderBase
   1602             <T extends MediaSession2, U extends BuilderBase<T, U, C>, C extends SessionCallback> {
   1603         final Context mContext;
   1604         MediaSession2ImplBase.BuilderBase<T, C> mBaseImpl;
   1605         MediaPlayerInterface mPlayer;
   1606         String mId;
   1607         Executor mCallbackExecutor;
   1608         C mCallback;
   1609         MediaPlaylistAgent mPlaylistAgent;
   1610         VolumeProviderCompat mVolumeProvider;
   1611         PendingIntent mSessionActivity;
   1612 
   1613         BuilderBase(Context context) {
   1614             if (context == null) {
   1615                 throw new IllegalArgumentException("context shouldn't be null");
   1616             }
   1617             mContext = context;
   1618             // Ensure non-null
   1619             mId = "";
   1620         }
   1621 
   1622         /**
   1623          * Sets the underlying {@link MediaPlayerInterface} for this session to dispatch incoming
   1624          * event to.
   1625          *
   1626          * @param player a {@link MediaPlayerInterface} that handles actual media playback in your
   1627          *               app.
   1628          */
   1629         @NonNull U setPlayer(@NonNull MediaPlayerInterface player) {
   1630             if (player == null) {
   1631                 throw new IllegalArgumentException("player shouldn't be null");
   1632             }
   1633             mBaseImpl.setPlayer(player);
   1634             return (U) this;
   1635         }
   1636 
   1637         /**
   1638          * Sets the {@link MediaPlaylistAgent} for this session to manages playlist of the
   1639          * underlying {@link MediaPlayerInterface}. The playlist agent should manage
   1640          * {@link MediaPlayerInterface} for calling
   1641          * {@link MediaPlayerInterface#setNextDataSources(List)}.
   1642          * <p>
   1643          * If the {@link MediaPlaylistAgent} isn't set, session will create the default playlist
   1644          * agent.
   1645          *
   1646          * @param playlistAgent a {@link MediaPlaylistAgent} that manages playlist of the
   1647          *                      {@code player}
   1648          */
   1649         U setPlaylistAgent(@NonNull MediaPlaylistAgent playlistAgent) {
   1650             if (playlistAgent == null) {
   1651                 throw new IllegalArgumentException("playlistAgent shouldn't be null");
   1652             }
   1653             mBaseImpl.setPlaylistAgent(playlistAgent);
   1654             return (U) this;
   1655         }
   1656 
   1657         /**
   1658          * Sets the {@link VolumeProviderCompat} for this session to handle volume events. If not
   1659          * set, system will adjust the appropriate stream volume for this session's player.
   1660          *
   1661          * @param volumeProvider The provider that will receive volume button events.
   1662          */
   1663         @NonNull U setVolumeProvider(@Nullable VolumeProviderCompat volumeProvider) {
   1664             mBaseImpl.setVolumeProvider(volumeProvider);
   1665             return (U) this;
   1666         }
   1667 
   1668         /**
   1669          * Set an intent for launching UI for this Session. This can be used as a
   1670          * quick link to an ongoing media screen. The intent should be for an
   1671          * activity that may be started using {@link Context#startActivity(Intent)}.
   1672          *
   1673          * @param pi The intent to launch to show UI for this session.
   1674          */
   1675         @NonNull U setSessionActivity(@Nullable PendingIntent pi) {
   1676             mBaseImpl.setSessionActivity(pi);
   1677             return (U) this;
   1678         }
   1679 
   1680         /**
   1681          * Set ID of the session. If it's not set, an empty string with used to create a session.
   1682          * <p>
   1683          * Use this if and only if your app supports multiple playback at the same time and also
   1684          * wants to provide external apps to have finer controls of them.
   1685          *
   1686          * @param id id of the session. Must be unique per package.
   1687          * @throws IllegalArgumentException if id is {@code null}
   1688          * @return
   1689          */
   1690         @NonNull U setId(@NonNull String id) {
   1691             if (id == null) {
   1692                 throw new IllegalArgumentException("id shouldn't be null");
   1693             }
   1694             mBaseImpl.setId(id);
   1695             return (U) this;
   1696         }
   1697 
   1698         /**
   1699          * Set callback for the session.
   1700          *
   1701          * @param executor callback executor
   1702          * @param callback session callback.
   1703          * @return
   1704          */
   1705         @NonNull U setSessionCallback(@NonNull Executor executor, @NonNull C callback) {
   1706             if (executor == null) {
   1707                 throw new IllegalArgumentException("executor shouldn't be null");
   1708             }
   1709             if (callback == null) {
   1710                 throw new IllegalArgumentException("callback shouldn't be null");
   1711             }
   1712             mBaseImpl.setSessionCallback(executor, callback);
   1713             return (U) this;
   1714         }
   1715 
   1716         /**
   1717          * Build {@link MediaSession2}.
   1718          *
   1719          * @return a new session
   1720          * @throws IllegalStateException if the session with the same id is already exists for the
   1721          *      package.
   1722          */
   1723         @NonNull T build() {
   1724             return mBaseImpl.build();
   1725         }
   1726 
   1727         void setImpl(MediaSession2ImplBase.BuilderBase<T, C> impl) {
   1728             mBaseImpl = impl;
   1729         }
   1730     }
   1731 }
   1732