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