1/*
2 * Copyright 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package androidx.media;
18
19import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20
21import android.annotation.TargetApi;
22import android.app.PendingIntent;
23import android.content.Context;
24import android.content.Intent;
25import android.media.AudioFocusRequest;
26import android.net.Uri;
27import android.os.Build;
28import android.os.Bundle;
29import android.os.IBinder;
30import android.os.RemoteException;
31import android.os.ResultReceiver;
32import android.support.v4.media.session.MediaSessionCompat;
33import android.support.v4.media.session.PlaybackStateCompat;
34
35import androidx.annotation.IntDef;
36import androidx.annotation.NonNull;
37import androidx.annotation.Nullable;
38import androidx.annotation.RestrictTo;
39import androidx.media.MediaController2.PlaybackInfo;
40import androidx.media.MediaPlayerInterface.BuffState;
41import androidx.media.MediaPlayerInterface.PlayerState;
42import androidx.media.MediaPlaylistAgent.PlaylistEventCallback;
43import androidx.media.MediaPlaylistAgent.RepeatMode;
44import androidx.media.MediaPlaylistAgent.ShuffleMode;
45
46import java.lang.annotation.Retention;
47import java.lang.annotation.RetentionPolicy;
48import java.util.List;
49import 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)
84public 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