MediaControllerCompat.java revision 7435f27d690f295c861db86f9e45475a205547b8
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.v4.media.session; 18 19import android.app.PendingIntent; 20import android.content.Context; 21import android.media.AudioManager; 22import android.net.Uri; 23import android.os.Bundle; 24import android.os.Handler; 25import android.os.IBinder; 26import android.os.Looper; 27import android.os.Message; 28import android.os.RemoteException; 29import android.os.ResultReceiver; 30import android.support.v4.app.BundleCompat; 31import android.support.v4.media.MediaMetadataCompat; 32import android.support.v4.media.RatingCompat; 33import android.support.v4.media.VolumeProviderCompat; 34import android.support.v4.media.session.MediaSessionCompat.QueueItem; 35import android.support.v4.media.session.PlaybackStateCompat.CustomAction; 36import android.text.TextUtils; 37import android.util.Log; 38import android.view.KeyEvent; 39 40import java.util.ArrayList; 41import java.util.HashMap; 42import java.util.List; 43 44/** 45 * Allows an app to interact with an ongoing media session. Media buttons and 46 * other commands can be sent to the session. A callback may be registered to 47 * receive updates from the session, such as metadata and play state changes. 48 * <p> 49 * A MediaController can be created if you have a {@link MediaSessionCompat.Token} 50 * from the session owner. 51 * <p> 52 * MediaController objects are thread-safe. 53 * <p> 54 * This is a helper for accessing features in {@link android.media.session.MediaSession} 55 * introduced after API level 4 in a backwards compatible fashion. 56 */ 57public final class MediaControllerCompat { 58 static final String TAG = "MediaControllerCompat"; 59 60 static final String COMMAND_GET_EXTRA_BINDER = 61 "android.support.v4.media.session.command.GET_EXTRA_BINDER"; 62 63 private final MediaControllerImpl mImpl; 64 private final MediaSessionCompat.Token mToken; 65 66 /** 67 * Creates a media controller from a session. 68 * 69 * @param session The session to be controlled. 70 */ 71 public MediaControllerCompat(Context context, MediaSessionCompat session) { 72 if (session == null) { 73 throw new IllegalArgumentException("session must not be null"); 74 } 75 mToken = session.getSessionToken(); 76 77 if (android.os.Build.VERSION.SDK_INT >= 24) { 78 mImpl = new MediaControllerImplApi24(context, session); 79 } else if (android.os.Build.VERSION.SDK_INT >= 23) { 80 mImpl = new MediaControllerImplApi23(context, session); 81 } else if (android.os.Build.VERSION.SDK_INT >= 21) { 82 mImpl = new MediaControllerImplApi21(context, session); 83 } else { 84 mImpl = new MediaControllerImplBase(mToken); 85 } 86 } 87 88 /** 89 * Creates a media controller from a session token which may have 90 * been obtained from another process. 91 * 92 * @param sessionToken The token of the session to be controlled. 93 * @throws RemoteException if the session is not accessible. 94 */ 95 public MediaControllerCompat(Context context, MediaSessionCompat.Token sessionToken) 96 throws RemoteException { 97 if (sessionToken == null) { 98 throw new IllegalArgumentException("sessionToken must not be null"); 99 } 100 mToken = sessionToken; 101 102 if (android.os.Build.VERSION.SDK_INT >= 24) { 103 mImpl = new MediaControllerImplApi24(context, sessionToken); 104 } else if (android.os.Build.VERSION.SDK_INT >= 23) { 105 mImpl = new MediaControllerImplApi23(context, sessionToken); 106 } else if (android.os.Build.VERSION.SDK_INT >= 21) { 107 mImpl = new MediaControllerImplApi21(context, sessionToken); 108 } else { 109 mImpl = new MediaControllerImplBase(mToken); 110 } 111 } 112 113 /** 114 * Get a {@link TransportControls} instance for this session. 115 * 116 * @return A controls instance 117 */ 118 public TransportControls getTransportControls() { 119 return mImpl.getTransportControls(); 120 } 121 122 /** 123 * Send the specified media button event to the session. Only media keys can 124 * be sent by this method, other keys will be ignored. 125 * 126 * @param keyEvent The media button event to dispatch. 127 * @return true if the event was sent to the session, false otherwise. 128 */ 129 public boolean dispatchMediaButtonEvent(KeyEvent keyEvent) { 130 if (keyEvent == null) { 131 throw new IllegalArgumentException("KeyEvent may not be null"); 132 } 133 return mImpl.dispatchMediaButtonEvent(keyEvent); 134 } 135 136 /** 137 * Get the current playback state for this session. 138 * 139 * @return The current PlaybackState or null 140 */ 141 public PlaybackStateCompat getPlaybackState() { 142 return mImpl.getPlaybackState(); 143 } 144 145 /** 146 * Get the current metadata for this session. 147 * 148 * @return The current MediaMetadata or null. 149 */ 150 public MediaMetadataCompat getMetadata() { 151 return mImpl.getMetadata(); 152 } 153 154 /** 155 * Get the current play queue for this session if one is set. If you only 156 * care about the current item {@link #getMetadata()} should be used. 157 * 158 * @return The current play queue or null. 159 */ 160 public List<MediaSessionCompat.QueueItem> getQueue() { 161 return mImpl.getQueue(); 162 } 163 164 /** 165 * Get the queue title for this session. 166 */ 167 public CharSequence getQueueTitle() { 168 return mImpl.getQueueTitle(); 169 } 170 171 /** 172 * Get the extras for this session. 173 */ 174 public Bundle getExtras() { 175 return mImpl.getExtras(); 176 } 177 178 /** 179 * Get the rating type supported by the session. One of: 180 * <ul> 181 * <li>{@link RatingCompat#RATING_NONE}</li> 182 * <li>{@link RatingCompat#RATING_HEART}</li> 183 * <li>{@link RatingCompat#RATING_THUMB_UP_DOWN}</li> 184 * <li>{@link RatingCompat#RATING_3_STARS}</li> 185 * <li>{@link RatingCompat#RATING_4_STARS}</li> 186 * <li>{@link RatingCompat#RATING_5_STARS}</li> 187 * <li>{@link RatingCompat#RATING_PERCENTAGE}</li> 188 * </ul> 189 * 190 * @return The supported rating type 191 */ 192 public int getRatingType() { 193 return mImpl.getRatingType(); 194 } 195 196 /** 197 * Get the flags for this session. Flags are defined in 198 * {@link MediaSessionCompat}. 199 * 200 * @return The current set of flags for the session. 201 */ 202 public long getFlags() { 203 return mImpl.getFlags(); 204 } 205 206 /** 207 * Get the current playback info for this session. 208 * 209 * @return The current playback info or null. 210 */ 211 public PlaybackInfo getPlaybackInfo() { 212 return mImpl.getPlaybackInfo(); 213 } 214 215 /** 216 * Get an intent for launching UI associated with this session if one 217 * exists. 218 * 219 * @return A {@link PendingIntent} to launch UI or null. 220 */ 221 public PendingIntent getSessionActivity() { 222 return mImpl.getSessionActivity(); 223 } 224 225 /** 226 * Get the token for the session this controller is connected to. 227 * 228 * @return The session's token. 229 */ 230 public MediaSessionCompat.Token getSessionToken() { 231 return mToken; 232 } 233 234 /** 235 * Set the volume of the output this session is playing on. The command will 236 * be ignored if it does not support 237 * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in 238 * {@link AudioManager} may be used to affect the handling. 239 * 240 * @see #getPlaybackInfo() 241 * @param value The value to set it to, between 0 and the reported max. 242 * @param flags Flags from {@link AudioManager} to include with the volume 243 * request. 244 */ 245 public void setVolumeTo(int value, int flags) { 246 mImpl.setVolumeTo(value, flags); 247 } 248 249 /** 250 * Adjust the volume of the output this session is playing on. The direction 251 * must be one of {@link AudioManager#ADJUST_LOWER}, 252 * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}. 253 * The command will be ignored if the session does not support 254 * {@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE} or 255 * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in 256 * {@link AudioManager} may be used to affect the handling. 257 * 258 * @see #getPlaybackInfo() 259 * @param direction The direction to adjust the volume in. 260 * @param flags Any flags to pass with the command. 261 */ 262 public void adjustVolume(int direction, int flags) { 263 mImpl.adjustVolume(direction, flags); 264 } 265 266 /** 267 * Adds a callback to receive updates from the Session. Updates will be 268 * posted on the caller's thread. 269 * 270 * @param callback The callback object, must not be null. 271 */ 272 public void registerCallback(Callback callback) { 273 registerCallback(callback, null); 274 } 275 276 /** 277 * Adds a callback to receive updates from the session. Updates will be 278 * posted on the specified handler's thread. 279 * 280 * @param callback The callback object, must not be null. 281 * @param handler The handler to post updates on. If null the callers thread 282 * will be used. 283 */ 284 public void registerCallback(Callback callback, Handler handler) { 285 if (callback == null) { 286 throw new IllegalArgumentException("callback cannot be null"); 287 } 288 if (handler == null) { 289 handler = new Handler(); 290 } 291 mImpl.registerCallback(callback, handler); 292 } 293 294 /** 295 * Stop receiving updates on the specified callback. If an update has 296 * already been posted you may still receive it after calling this method. 297 * 298 * @param callback The callback to remove 299 */ 300 public void unregisterCallback(Callback callback) { 301 if (callback == null) { 302 throw new IllegalArgumentException("callback cannot be null"); 303 } 304 mImpl.unregisterCallback(callback); 305 } 306 307 /** 308 * Sends a generic command to the session. It is up to the session creator 309 * to decide what commands and parameters they will support. As such, 310 * commands should only be sent to sessions that the controller owns. 311 * 312 * @param command The command to send 313 * @param params Any parameters to include with the command 314 * @param cb The callback to receive the result on 315 */ 316 public void sendCommand(String command, Bundle params, ResultReceiver cb) { 317 if (TextUtils.isEmpty(command)) { 318 throw new IllegalArgumentException("command cannot be null or empty"); 319 } 320 mImpl.sendCommand(command, params, cb); 321 } 322 323 /** 324 * Get the session owner's package name. 325 * 326 * @return The package name of of the session owner. 327 */ 328 public String getPackageName() { 329 return mImpl.getPackageName(); 330 } 331 332 /** 333 * Gets the underlying framework 334 * {@link android.media.session.MediaController} object. 335 * <p> 336 * This method is only supported on API 21+. 337 * </p> 338 * 339 * @return The underlying {@link android.media.session.MediaController} 340 * object, or null if none. 341 */ 342 public Object getMediaController() { 343 return mImpl.getMediaController(); 344 } 345 346 /** 347 * Callback for receiving updates on from the session. A Callback can be 348 * registered using {@link #registerCallback} 349 */ 350 public static abstract class Callback implements IBinder.DeathRecipient { 351 private final Object mCallbackObj; 352 MessageHandler mHandler; 353 boolean mHasExtraCallback; 354 355 boolean mRegistered = false; 356 357 public Callback() { 358 if (android.os.Build.VERSION.SDK_INT >= 21) { 359 mCallbackObj = MediaControllerCompatApi21.createCallback(new StubApi21()); 360 } else { 361 mCallbackObj = new StubCompat(); 362 } 363 } 364 365 /** 366 * Override to handle the session being destroyed. The session is no 367 * longer valid after this call and calls to it will be ignored. 368 */ 369 public void onSessionDestroyed() { 370 } 371 372 /** 373 * Override to handle custom events sent by the session owner without a 374 * specified interface. Controllers should only handle these for 375 * sessions they own. 376 * 377 * @param event The event from the session. 378 * @param extras Optional parameters for the event. 379 */ 380 public void onSessionEvent(String event, Bundle extras) { 381 } 382 383 /** 384 * Override to handle changes in playback state. 385 * 386 * @param state The new playback state of the session 387 */ 388 public void onPlaybackStateChanged(PlaybackStateCompat state) { 389 } 390 391 /** 392 * Override to handle changes to the current metadata. 393 * 394 * @param metadata The current metadata for the session or null if none. 395 * @see MediaMetadataCompat 396 */ 397 public void onMetadataChanged(MediaMetadataCompat metadata) { 398 } 399 400 /** 401 * Override to handle changes to items in the queue. 402 * 403 * @see MediaSessionCompat.QueueItem 404 * @param queue A list of items in the current play queue. It should 405 * include the currently playing item as well as previous and 406 * upcoming items if applicable. 407 */ 408 public void onQueueChanged(List<MediaSessionCompat.QueueItem> queue) { 409 } 410 411 /** 412 * Override to handle changes to the queue title. 413 * 414 * @param title The title that should be displayed along with the play 415 * queue such as "Now Playing". May be null if there is no 416 * such title. 417 */ 418 public void onQueueTitleChanged(CharSequence title) { 419 } 420 421 /** 422 * Override to handle chagnes to the {@link MediaSessionCompat} extras. 423 * 424 * @param extras The extras that can include other information 425 * associated with the {@link MediaSessionCompat}. 426 */ 427 public void onExtrasChanged(Bundle extras) { 428 } 429 430 /** 431 * Override to handle changes to the audio info. 432 * 433 * @param info The current audio info for this session. 434 */ 435 public void onAudioInfoChanged(PlaybackInfo info) { 436 } 437 438 @Override 439 public void binderDied() { 440 onSessionDestroyed(); 441 } 442 443 /** 444 * Set the handler to use for pre 21 callbacks. 445 */ 446 private void setHandler(Handler handler) { 447 mHandler = new MessageHandler(handler.getLooper()); 448 } 449 450 private class StubApi21 implements MediaControllerCompatApi21.Callback { 451 StubApi21() { 452 } 453 454 @Override 455 public void onSessionDestroyed() { 456 Callback.this.onSessionDestroyed(); 457 } 458 459 @Override 460 public void onSessionEvent(String event, Bundle extras) { 461 if (mHasExtraCallback && android.os.Build.VERSION.SDK_INT < 23) { 462 // Ignore. ExtraCallback will handle this. 463 } else { 464 Callback.this.onSessionEvent(event, extras); 465 } 466 } 467 468 @Override 469 public void onPlaybackStateChanged(Object stateObj) { 470 if (mHasExtraCallback && android.os.Build.VERSION.SDK_INT < 22) { 471 // Ignore. ExtraCallback will handle this. 472 } else { 473 Callback.this.onPlaybackStateChanged( 474 PlaybackStateCompat.fromPlaybackState(stateObj)); 475 } 476 } 477 478 @Override 479 public void onMetadataChanged(Object metadataObj) { 480 Callback.this.onMetadataChanged(MediaMetadataCompat.fromMediaMetadata(metadataObj)); 481 } 482 483 @Override 484 public void onQueueChanged(List<?> queue) { 485 Callback.this.onQueueChanged(QueueItem.fromQueueItemList(queue)); 486 } 487 488 @Override 489 public void onQueueTitleChanged(CharSequence title) { 490 Callback.this.onQueueTitleChanged(title); 491 } 492 493 @Override 494 public void onExtrasChanged(Bundle extras) { 495 Callback.this.onExtrasChanged(extras); 496 } 497 498 @Override 499 public void onAudioInfoChanged( 500 int type, int stream, int control, int max, int current) { 501 Callback.this.onAudioInfoChanged( 502 new PlaybackInfo(type, stream, control, max, current)); 503 } 504 } 505 506 private class StubCompat extends IMediaControllerCallback.Stub { 507 508 StubCompat() { 509 } 510 511 @Override 512 public void onEvent(String event, Bundle extras) throws RemoteException { 513 mHandler.post(MessageHandler.MSG_EVENT, event, extras); 514 } 515 516 @Override 517 public void onSessionDestroyed() throws RemoteException { 518 mHandler.post(MessageHandler.MSG_DESTROYED, null, null); 519 } 520 521 @Override 522 public void onPlaybackStateChanged(PlaybackStateCompat state) throws RemoteException { 523 mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE, state, null); 524 } 525 526 @Override 527 public void onMetadataChanged(MediaMetadataCompat metadata) throws RemoteException { 528 mHandler.post(MessageHandler.MSG_UPDATE_METADATA, metadata, null); 529 } 530 531 @Override 532 public void onQueueChanged(List<QueueItem> queue) throws RemoteException { 533 mHandler.post(MessageHandler.MSG_UPDATE_QUEUE, queue, null); 534 } 535 536 @Override 537 public void onQueueTitleChanged(CharSequence title) throws RemoteException { 538 mHandler.post(MessageHandler.MSG_UPDATE_QUEUE_TITLE, title, null); 539 } 540 541 @Override 542 public void onExtrasChanged(Bundle extras) throws RemoteException { 543 mHandler.post(MessageHandler.MSG_UPDATE_EXTRAS, extras, null); 544 } 545 546 @Override 547 public void onVolumeInfoChanged(ParcelableVolumeInfo info) throws RemoteException { 548 PlaybackInfo pi = null; 549 if (info != null) { 550 pi = new PlaybackInfo(info.volumeType, info.audioStream, info.controlType, 551 info.maxVolume, info.currentVolume); 552 } 553 mHandler.post(MessageHandler.MSG_UPDATE_VOLUME, pi, null); 554 } 555 } 556 557 private class MessageHandler extends Handler { 558 private static final int MSG_EVENT = 1; 559 private static final int MSG_UPDATE_PLAYBACK_STATE = 2; 560 private static final int MSG_UPDATE_METADATA = 3; 561 private static final int MSG_UPDATE_VOLUME = 4; 562 private static final int MSG_UPDATE_QUEUE = 5; 563 private static final int MSG_UPDATE_QUEUE_TITLE = 6; 564 private static final int MSG_UPDATE_EXTRAS = 7; 565 private static final int MSG_DESTROYED = 8; 566 567 public MessageHandler(Looper looper) { 568 super(looper); 569 } 570 571 @Override 572 public void handleMessage(Message msg) { 573 if (!mRegistered) { 574 return; 575 } 576 switch (msg.what) { 577 case MSG_EVENT: 578 onSessionEvent((String) msg.obj, msg.getData()); 579 break; 580 case MSG_UPDATE_PLAYBACK_STATE: 581 onPlaybackStateChanged((PlaybackStateCompat) msg.obj); 582 break; 583 case MSG_UPDATE_METADATA: 584 onMetadataChanged((MediaMetadataCompat) msg.obj); 585 break; 586 case MSG_UPDATE_QUEUE: 587 onQueueChanged((List<MediaSessionCompat.QueueItem>) msg.obj); 588 break; 589 case MSG_UPDATE_QUEUE_TITLE: 590 onQueueTitleChanged((CharSequence) msg.obj); 591 break; 592 case MSG_UPDATE_EXTRAS: 593 onExtrasChanged((Bundle) msg.obj); 594 break; 595 case MSG_UPDATE_VOLUME: 596 onAudioInfoChanged((PlaybackInfo) msg.obj); 597 break; 598 case MSG_DESTROYED: 599 onSessionDestroyed(); 600 break; 601 } 602 } 603 604 public void post(int what, Object obj, Bundle data) { 605 Message msg = obtainMessage(what, obj); 606 msg.setData(data); 607 msg.sendToTarget(); 608 } 609 } 610 } 611 612 /** 613 * Interface for controlling media playback on a session. This allows an app 614 * to send media transport commands to the session. 615 */ 616 public static abstract class TransportControls { 617 TransportControls() { 618 } 619 620 /** 621 * Request that the player prepare its playback without audio focus. In other words, other 622 * session can continue to play during the preparation of this session. This method can be 623 * used to speed up the start of the playback. Once the preparation is done, the session 624 * will change its playback state to {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards, 625 * {@link #play} can be called to start playback. If the preparation is not needed, 626 * {@link #play} can be directly called without this method. 627 */ 628 public abstract void prepare(); 629 630 /** 631 * Request that the player prepare playback for a specific media id. In other words, other 632 * session can continue to play during the preparation of this session. This method can be 633 * used to speed up the start of the playback. Once the preparation is 634 * done, the session will change its playback state to 635 * {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards, {@link #play} can be called to 636 * start playback. If the preparation is not needed, {@link #playFromMediaId} can 637 * be directly called without this method. 638 * 639 * @param mediaId The id of the requested media. 640 * @param extras Optional extras that can include extra information about the media item 641 * to be prepared. 642 */ 643 public abstract void prepareFromMediaId(String mediaId, Bundle extras); 644 645 /** 646 * Request that the player prepare playback for a specific search query. 647 * An empty or null query should be treated as a request to prepare any 648 * music. In other words, other session can continue to play during 649 * the preparation of this session. This method can be used to speed up the start of the 650 * playback. Once the preparation is done, the session will change its playback state to 651 * {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards, {@link #play} can be called to 652 * start playback. If the preparation is not needed, {@link #playFromSearch} can be directly 653 * called without this method. 654 * 655 * @param query The search query. 656 * @param extras Optional extras that can include extra information 657 * about the query. 658 */ 659 public abstract void prepareFromSearch(String query, Bundle extras); 660 661 /** 662 * Request that the player prepare playback for a specific {@link Uri}. 663 * In other words, other session can continue to play during the preparation of this 664 * session. This method can be used to speed up the start of the playback. 665 * Once the preparation is done, the session will change its playback state to 666 * {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards, {@link #play} can be called to 667 * start playback. If the preparation is not needed, {@link #playFromUri} can be directly 668 * called without this method. 669 * 670 * @param uri The URI of the requested media. 671 * @param extras Optional extras that can include extra information about the media item 672 * to be prepared. 673 */ 674 public abstract void prepareFromUri(Uri uri, Bundle extras); 675 676 /** 677 * Request that the player start its playback at its current position. 678 */ 679 public abstract void play(); 680 681 /** 682 * Request that the player start playback for a specific {@link Uri}. 683 * 684 * @param mediaId The uri of the requested media. 685 * @param extras Optional extras that can include extra information 686 * about the media item to be played. 687 */ 688 public abstract void playFromMediaId(String mediaId, Bundle extras); 689 690 /** 691 * Request that the player start playback for a specific search query. 692 * An empty or null query should be treated as a request to play any 693 * music. 694 * 695 * @param query The search query. 696 * @param extras Optional extras that can include extra information 697 * about the query. 698 */ 699 public abstract void playFromSearch(String query, Bundle extras); 700 701 /** 702 * Request that the player start playback for a specific {@link Uri}. 703 * 704 * @param uri The URI of the requested media. 705 * @param extras Optional extras that can include extra information about the media item 706 * to be played. 707 */ 708 public abstract void playFromUri(Uri uri, Bundle extras); 709 710 /** 711 * Play an item with a specific id in the play queue. If you specify an 712 * id that is not in the play queue, the behavior is undefined. 713 */ 714 public abstract void skipToQueueItem(long id); 715 716 /** 717 * Request that the player pause its playback and stay at its current 718 * position. 719 */ 720 public abstract void pause(); 721 722 /** 723 * Request that the player stop its playback; it may clear its state in 724 * whatever way is appropriate. 725 */ 726 public abstract void stop(); 727 728 /** 729 * Move to a new location in the media stream. 730 * 731 * @param pos Position to move to, in milliseconds. 732 */ 733 public abstract void seekTo(long pos); 734 735 /** 736 * Start fast forwarding. If playback is already fast forwarding this 737 * may increase the rate. 738 */ 739 public abstract void fastForward(); 740 741 /** 742 * Skip to the next item. 743 */ 744 public abstract void skipToNext(); 745 746 /** 747 * Start rewinding. If playback is already rewinding this may increase 748 * the rate. 749 */ 750 public abstract void rewind(); 751 752 /** 753 * Skip to the previous item. 754 */ 755 public abstract void skipToPrevious(); 756 757 /** 758 * Rate the current content. This will cause the rating to be set for 759 * the current user. The Rating type must match the type returned by 760 * {@link #getRatingType()}. 761 * 762 * @param rating The rating to set for the current content 763 */ 764 public abstract void setRating(RatingCompat rating); 765 766 /** 767 * Send a custom action for the {@link MediaSessionCompat} to perform. 768 * 769 * @param customAction The action to perform. 770 * @param args Optional arguments to supply to the 771 * {@link MediaSessionCompat} for this custom action. 772 */ 773 public abstract void sendCustomAction(PlaybackStateCompat.CustomAction customAction, 774 Bundle args); 775 776 /** 777 * Send the id and args from a custom action for the 778 * {@link MediaSessionCompat} to perform. 779 * 780 * @see #sendCustomAction(PlaybackStateCompat.CustomAction action, 781 * Bundle args) 782 * @param action The action identifier of the 783 * {@link PlaybackStateCompat.CustomAction} as specified by 784 * the {@link MediaSessionCompat}. 785 * @param args Optional arguments to supply to the 786 * {@link MediaSessionCompat} for this custom action. 787 */ 788 public abstract void sendCustomAction(String action, Bundle args); 789 } 790 791 /** 792 * Holds information about the way volume is handled for this session. 793 */ 794 public static final class PlaybackInfo { 795 /** 796 * The session uses local playback. 797 */ 798 public static final int PLAYBACK_TYPE_LOCAL = 1; 799 /** 800 * The session uses remote playback. 801 */ 802 public static final int PLAYBACK_TYPE_REMOTE = 2; 803 804 private final int mPlaybackType; 805 // TODO update audio stream with AudioAttributes support version 806 private final int mAudioStream; 807 private final int mVolumeControl; 808 private final int mMaxVolume; 809 private final int mCurrentVolume; 810 811 PlaybackInfo(int type, int stream, int control, int max, int current) { 812 mPlaybackType = type; 813 mAudioStream = stream; 814 mVolumeControl = control; 815 mMaxVolume = max; 816 mCurrentVolume = current; 817 } 818 819 /** 820 * Get the type of volume handling, either local or remote. One of: 821 * <ul> 822 * <li>{@link PlaybackInfo#PLAYBACK_TYPE_LOCAL}</li> 823 * <li>{@link PlaybackInfo#PLAYBACK_TYPE_REMOTE}</li> 824 * </ul> 825 * 826 * @return The type of volume handling this session is using. 827 */ 828 public int getPlaybackType() { 829 return mPlaybackType; 830 } 831 832 /** 833 * Get the stream this is currently controlling volume on. When the volume 834 * type is {@link PlaybackInfo#PLAYBACK_TYPE_REMOTE} this value does not 835 * have meaning and should be ignored. 836 * 837 * @return The stream this session is playing on. 838 */ 839 public int getAudioStream() { 840 // TODO switch to AudioAttributesCompat when it is added. 841 return mAudioStream; 842 } 843 844 /** 845 * Get the type of volume control that can be used. One of: 846 * <ul> 847 * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}</li> 848 * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE}</li> 849 * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_FIXED}</li> 850 * </ul> 851 * 852 * @return The type of volume control that may be used with this 853 * session. 854 */ 855 public int getVolumeControl() { 856 return mVolumeControl; 857 } 858 859 /** 860 * Get the maximum volume that may be set for this session. 861 * 862 * @return The maximum allowed volume where this session is playing. 863 */ 864 public int getMaxVolume() { 865 return mMaxVolume; 866 } 867 868 /** 869 * Get the current volume for this session. 870 * 871 * @return The current volume where this session is playing. 872 */ 873 public int getCurrentVolume() { 874 return mCurrentVolume; 875 } 876 } 877 878 interface MediaControllerImpl { 879 void registerCallback(Callback callback, Handler handler); 880 881 void unregisterCallback(Callback callback); 882 boolean dispatchMediaButtonEvent(KeyEvent keyEvent); 883 TransportControls getTransportControls(); 884 PlaybackStateCompat getPlaybackState(); 885 MediaMetadataCompat getMetadata(); 886 887 List<MediaSessionCompat.QueueItem> getQueue(); 888 CharSequence getQueueTitle(); 889 Bundle getExtras(); 890 int getRatingType(); 891 long getFlags(); 892 PlaybackInfo getPlaybackInfo(); 893 PendingIntent getSessionActivity(); 894 895 void setVolumeTo(int value, int flags); 896 void adjustVolume(int direction, int flags); 897 void sendCommand(String command, Bundle params, ResultReceiver cb); 898 899 String getPackageName(); 900 Object getMediaController(); 901 } 902 903 static class MediaControllerImplBase implements MediaControllerImpl { 904 private MediaSessionCompat.Token mToken; 905 private IMediaSession mBinder; 906 private TransportControls mTransportControls; 907 908 public MediaControllerImplBase(MediaSessionCompat.Token token) { 909 mToken = token; 910 mBinder = IMediaSession.Stub.asInterface((IBinder) token.getToken()); 911 } 912 913 @Override 914 public void registerCallback(Callback callback, Handler handler) { 915 if (callback == null) { 916 throw new IllegalArgumentException("callback may not be null."); 917 } 918 try { 919 mBinder.asBinder().linkToDeath(callback, 0); 920 mBinder.registerCallbackListener((IMediaControllerCallback) callback.mCallbackObj); 921 callback.setHandler(handler); 922 callback.mRegistered = true; 923 } catch (RemoteException e) { 924 Log.e(TAG, "Dead object in registerCallback. " + e); 925 callback.onSessionDestroyed(); 926 } 927 } 928 929 @Override 930 public void unregisterCallback(Callback callback) { 931 if (callback == null) { 932 throw new IllegalArgumentException("callback may not be null."); 933 } 934 try { 935 mBinder.unregisterCallbackListener( 936 (IMediaControllerCallback) callback.mCallbackObj); 937 mBinder.asBinder().unlinkToDeath(callback, 0); 938 callback.mRegistered = false; 939 } catch (RemoteException e) { 940 Log.e(TAG, "Dead object in unregisterCallback. " + e); 941 } 942 } 943 944 @Override 945 public boolean dispatchMediaButtonEvent(KeyEvent event) { 946 if (event == null) { 947 throw new IllegalArgumentException("event may not be null."); 948 } 949 try { 950 mBinder.sendMediaButton(event); 951 } catch (RemoteException e) { 952 Log.e(TAG, "Dead object in dispatchMediaButtonEvent. " + e); 953 } 954 return false; 955 } 956 957 @Override 958 public TransportControls getTransportControls() { 959 if (mTransportControls == null) { 960 mTransportControls = new TransportControlsBase(mBinder); 961 } 962 963 return mTransportControls; 964 } 965 966 @Override 967 public PlaybackStateCompat getPlaybackState() { 968 try { 969 return mBinder.getPlaybackState(); 970 } catch (RemoteException e) { 971 Log.e(TAG, "Dead object in getPlaybackState. " + e); 972 } 973 return null; 974 } 975 976 @Override 977 public MediaMetadataCompat getMetadata() { 978 try { 979 return mBinder.getMetadata(); 980 } catch (RemoteException e) { 981 Log.e(TAG, "Dead object in getMetadata. " + e); 982 } 983 return null; 984 } 985 986 @Override 987 public List<MediaSessionCompat.QueueItem> getQueue() { 988 try { 989 return mBinder.getQueue(); 990 } catch (RemoteException e) { 991 Log.e(TAG, "Dead object in getQueue. " + e); 992 } 993 return null; 994 } 995 996 @Override 997 public CharSequence getQueueTitle() { 998 try { 999 return mBinder.getQueueTitle(); 1000 } catch (RemoteException e) { 1001 Log.e(TAG, "Dead object in getQueueTitle. " + e); 1002 } 1003 return null; 1004 } 1005 1006 @Override 1007 public Bundle getExtras() { 1008 try { 1009 return mBinder.getExtras(); 1010 } catch (RemoteException e) { 1011 Log.e(TAG, "Dead object in getExtras. " + e); 1012 } 1013 return null; 1014 } 1015 1016 @Override 1017 public int getRatingType() { 1018 try { 1019 return mBinder.getRatingType(); 1020 } catch (RemoteException e) { 1021 Log.e(TAG, "Dead object in getRatingType. " + e); 1022 } 1023 return 0; 1024 } 1025 1026 @Override 1027 public long getFlags() { 1028 try { 1029 return mBinder.getFlags(); 1030 } catch (RemoteException e) { 1031 Log.e(TAG, "Dead object in getFlags. " + e); 1032 } 1033 return 0; 1034 } 1035 1036 @Override 1037 public PlaybackInfo getPlaybackInfo() { 1038 try { 1039 ParcelableVolumeInfo info = mBinder.getVolumeAttributes(); 1040 PlaybackInfo pi = new PlaybackInfo(info.volumeType, info.audioStream, 1041 info.controlType, info.maxVolume, info.currentVolume); 1042 return pi; 1043 } catch (RemoteException e) { 1044 Log.e(TAG, "Dead object in getPlaybackInfo. " + e); 1045 } 1046 return null; 1047 } 1048 1049 @Override 1050 public PendingIntent getSessionActivity() { 1051 try { 1052 return mBinder.getLaunchPendingIntent(); 1053 } catch (RemoteException e) { 1054 Log.e(TAG, "Dead object in getSessionActivity. " + e); 1055 } 1056 return null; 1057 } 1058 1059 @Override 1060 public void setVolumeTo(int value, int flags) { 1061 try { 1062 mBinder.setVolumeTo(value, flags, null); 1063 } catch (RemoteException e) { 1064 Log.e(TAG, "Dead object in setVolumeTo. " + e); 1065 } 1066 } 1067 1068 @Override 1069 public void adjustVolume(int direction, int flags) { 1070 try { 1071 mBinder.adjustVolume(direction, flags, null); 1072 } catch (RemoteException e) { 1073 Log.e(TAG, "Dead object in adjustVolume. " + e); 1074 } 1075 } 1076 1077 @Override 1078 public void sendCommand(String command, Bundle params, ResultReceiver cb) { 1079 try { 1080 mBinder.sendCommand(command, params, 1081 new MediaSessionCompat.ResultReceiverWrapper(cb)); 1082 } catch (RemoteException e) { 1083 Log.e(TAG, "Dead object in sendCommand. " + e); 1084 } 1085 } 1086 1087 @Override 1088 public String getPackageName() { 1089 try { 1090 return mBinder.getPackageName(); 1091 } catch (RemoteException e) { 1092 Log.e(TAG, "Dead object in getPackageName. " + e); 1093 } 1094 return null; 1095 } 1096 1097 @Override 1098 public Object getMediaController() { 1099 return null; 1100 } 1101 } 1102 1103 static class TransportControlsBase extends TransportControls { 1104 private IMediaSession mBinder; 1105 1106 public TransportControlsBase(IMediaSession binder) { 1107 mBinder = binder; 1108 } 1109 1110 @Override 1111 public void prepare() { 1112 try { 1113 mBinder.prepare(); 1114 } catch (RemoteException e) { 1115 Log.e(TAG, "Dead object in prepare. " + e); 1116 } 1117 } 1118 1119 @Override 1120 public void prepareFromMediaId(String mediaId, Bundle extras) { 1121 try { 1122 mBinder.prepareFromMediaId(mediaId, extras); 1123 } catch (RemoteException e) { 1124 Log.e(TAG, "Dead object in prepareFromMediaId. " + e); 1125 } 1126 } 1127 1128 @Override 1129 public void prepareFromSearch(String query, Bundle extras) { 1130 try { 1131 mBinder.prepareFromSearch(query, extras); 1132 } catch (RemoteException e) { 1133 Log.e(TAG, "Dead object in prepareFromSearch. " + e); 1134 } 1135 } 1136 1137 @Override 1138 public void prepareFromUri(Uri uri, Bundle extras) { 1139 try { 1140 mBinder.prepareFromUri(uri, extras); 1141 } catch (RemoteException e) { 1142 Log.e(TAG, "Dead object in prepareFromUri. " + e); 1143 } 1144 } 1145 1146 @Override 1147 public void play() { 1148 try { 1149 mBinder.play(); 1150 } catch (RemoteException e) { 1151 Log.e(TAG, "Dead object in play. " + e); 1152 } 1153 } 1154 1155 @Override 1156 public void playFromMediaId(String mediaId, Bundle extras) { 1157 try { 1158 mBinder.playFromMediaId(mediaId, extras); 1159 } catch (RemoteException e) { 1160 Log.e(TAG, "Dead object in playFromMediaId. " + e); 1161 } 1162 } 1163 1164 @Override 1165 public void playFromSearch(String query, Bundle extras) { 1166 try { 1167 mBinder.playFromSearch(query, extras); 1168 } catch (RemoteException e) { 1169 Log.e(TAG, "Dead object in playFromSearch. " + e); 1170 } 1171 } 1172 1173 @Override 1174 public void playFromUri(Uri uri, Bundle extras) { 1175 try { 1176 mBinder.playFromUri(uri, extras); 1177 } catch (RemoteException e) { 1178 Log.e(TAG, "Dead object in playFromUri. " + e); 1179 } 1180 } 1181 1182 @Override 1183 public void skipToQueueItem(long id) { 1184 try { 1185 mBinder.skipToQueueItem(id); 1186 } catch (RemoteException e) { 1187 Log.e(TAG, "Dead object in skipToQueueItem. " + e); 1188 } 1189 } 1190 1191 @Override 1192 public void pause() { 1193 try { 1194 mBinder.pause(); 1195 } catch (RemoteException e) { 1196 Log.e(TAG, "Dead object in pause. " + e); 1197 } 1198 } 1199 1200 @Override 1201 public void stop() { 1202 try { 1203 mBinder.stop(); 1204 } catch (RemoteException e) { 1205 Log.e(TAG, "Dead object in stop. " + e); 1206 } 1207 } 1208 1209 @Override 1210 public void seekTo(long pos) { 1211 try { 1212 mBinder.seekTo(pos); 1213 } catch (RemoteException e) { 1214 Log.e(TAG, "Dead object in seekTo. " + e); 1215 } 1216 } 1217 1218 @Override 1219 public void fastForward() { 1220 try { 1221 mBinder.fastForward(); 1222 } catch (RemoteException e) { 1223 Log.e(TAG, "Dead object in fastForward. " + e); 1224 } 1225 } 1226 1227 @Override 1228 public void skipToNext() { 1229 try { 1230 mBinder.next(); 1231 } catch (RemoteException e) { 1232 Log.e(TAG, "Dead object in skipToNext. " + e); 1233 } 1234 } 1235 1236 @Override 1237 public void rewind() { 1238 try { 1239 mBinder.rewind(); 1240 } catch (RemoteException e) { 1241 Log.e(TAG, "Dead object in rewind. " + e); 1242 } 1243 } 1244 1245 @Override 1246 public void skipToPrevious() { 1247 try { 1248 mBinder.previous(); 1249 } catch (RemoteException e) { 1250 Log.e(TAG, "Dead object in skipToPrevious. " + e); 1251 } 1252 } 1253 1254 @Override 1255 public void setRating(RatingCompat rating) { 1256 try { 1257 mBinder.rate(rating); 1258 } catch (RemoteException e) { 1259 Log.e(TAG, "Dead object in setRating. " + e); 1260 } 1261 } 1262 1263 @Override 1264 public void sendCustomAction(CustomAction customAction, Bundle args) { 1265 sendCustomAction(customAction.getAction(), args); 1266 } 1267 1268 @Override 1269 public void sendCustomAction(String action, Bundle args) { 1270 try { 1271 mBinder.sendCustomAction(action, args); 1272 } catch (RemoteException e) { 1273 Log.e(TAG, "Dead object in sendCustomAction. " + e); 1274 } 1275 } 1276 } 1277 1278 static class MediaControllerImplApi21 implements MediaControllerImpl { 1279 protected final Object mControllerObj; 1280 1281 // Extra binder is used for applying the framework change of new APIs and bug fixes 1282 // after API 21. 1283 private IMediaSession mExtraBinder; 1284 private HashMap<Callback, ExtraCallback> mCallbackMap = new HashMap<>(); 1285 private List<Callback> mPendingCallbacks; 1286 1287 public MediaControllerImplApi21(Context context, MediaSessionCompat session) { 1288 mControllerObj = MediaControllerCompatApi21.fromToken(context, 1289 session.getSessionToken().getToken()); 1290 requestExtraBinder(); 1291 } 1292 1293 public MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken) 1294 throws RemoteException { 1295 mControllerObj = MediaControllerCompatApi21.fromToken(context, 1296 sessionToken.getToken()); 1297 if (mControllerObj == null) throw new RemoteException(); 1298 requestExtraBinder(); 1299 } 1300 1301 @Override 1302 public final void registerCallback(Callback callback, Handler handler) { 1303 MediaControllerCompatApi21.registerCallback( 1304 mControllerObj, callback.mCallbackObj, handler); 1305 if (mExtraBinder != null) { 1306 callback.setHandler(handler); 1307 ExtraCallback extraCallback = new ExtraCallback(callback); 1308 mCallbackMap.put(callback, extraCallback); 1309 callback.mHasExtraCallback = true; 1310 try { 1311 mExtraBinder.registerCallbackListener(extraCallback); 1312 } catch (RemoteException e) { 1313 Log.e(TAG, "Dead object in registerCallback. " + e); 1314 } 1315 } else { 1316 if (mPendingCallbacks == null) { 1317 mPendingCallbacks = new ArrayList<>(); 1318 } 1319 callback.setHandler(handler); 1320 mPendingCallbacks.add(callback); 1321 } 1322 } 1323 1324 @Override 1325 public final void unregisterCallback(Callback callback) { 1326 MediaControllerCompatApi21.unregisterCallback(mControllerObj, callback.mCallbackObj); 1327 if (mExtraBinder != null) { 1328 try { 1329 ExtraCallback extraCallback = mCallbackMap.remove(callback); 1330 if (extraCallback != null) { 1331 mExtraBinder.unregisterCallbackListener(extraCallback); 1332 } 1333 } catch (RemoteException e) { 1334 Log.e(TAG, "Dead object in unregisterCallback. " + e); 1335 } 1336 } else { 1337 if (mPendingCallbacks == null) { 1338 mPendingCallbacks = new ArrayList<>(); 1339 } 1340 mPendingCallbacks.remove(callback); 1341 } 1342 } 1343 1344 @Override 1345 public boolean dispatchMediaButtonEvent(KeyEvent event) { 1346 return MediaControllerCompatApi21.dispatchMediaButtonEvent(mControllerObj, event); 1347 } 1348 1349 @Override 1350 public TransportControls getTransportControls() { 1351 Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj); 1352 return controlsObj != null ? new TransportControlsApi21(controlsObj) : null; 1353 } 1354 1355 @Override 1356 public PlaybackStateCompat getPlaybackState() { 1357 if (android.os.Build.VERSION.SDK_INT < 22 && mExtraBinder != null) { 1358 try { 1359 return mExtraBinder.getPlaybackState(); 1360 } catch (RemoteException e) { 1361 Log.e(TAG, "Dead object in getPlaybackState. " + e); 1362 } 1363 } 1364 Object stateObj = MediaControllerCompatApi21.getPlaybackState(mControllerObj); 1365 return stateObj != null ? PlaybackStateCompat.fromPlaybackState(stateObj) : null; 1366 } 1367 1368 @Override 1369 public MediaMetadataCompat getMetadata() { 1370 Object metadataObj = MediaControllerCompatApi21.getMetadata(mControllerObj); 1371 return metadataObj != null ? MediaMetadataCompat.fromMediaMetadata(metadataObj) : null; 1372 } 1373 1374 @Override 1375 public List<MediaSessionCompat.QueueItem> getQueue() { 1376 List<Object> queueObjs = MediaControllerCompatApi21.getQueue(mControllerObj); 1377 return queueObjs != null ? MediaSessionCompat.QueueItem.fromQueueItemList(queueObjs) 1378 : null; 1379 } 1380 1381 @Override 1382 public CharSequence getQueueTitle() { 1383 return MediaControllerCompatApi21.getQueueTitle(mControllerObj); 1384 } 1385 1386 @Override 1387 public Bundle getExtras() { 1388 return MediaControllerCompatApi21.getExtras(mControllerObj); 1389 } 1390 1391 @Override 1392 public int getRatingType() { 1393 if (android.os.Build.VERSION.SDK_INT < 22 && mExtraBinder != null) { 1394 try { 1395 return mExtraBinder.getRatingType(); 1396 } catch (RemoteException e) { 1397 Log.e(TAG, "Dead object in getRatingType. " + e); 1398 } 1399 } 1400 return MediaControllerCompatApi21.getRatingType(mControllerObj); 1401 } 1402 1403 @Override 1404 public long getFlags() { 1405 return MediaControllerCompatApi21.getFlags(mControllerObj); 1406 } 1407 1408 @Override 1409 public PlaybackInfo getPlaybackInfo() { 1410 Object volumeInfoObj = MediaControllerCompatApi21.getPlaybackInfo(mControllerObj); 1411 return volumeInfoObj != null ? new PlaybackInfo( 1412 MediaControllerCompatApi21.PlaybackInfo.getPlaybackType(volumeInfoObj), 1413 MediaControllerCompatApi21.PlaybackInfo.getLegacyAudioStream(volumeInfoObj), 1414 MediaControllerCompatApi21.PlaybackInfo.getVolumeControl(volumeInfoObj), 1415 MediaControllerCompatApi21.PlaybackInfo.getMaxVolume(volumeInfoObj), 1416 MediaControllerCompatApi21.PlaybackInfo.getCurrentVolume(volumeInfoObj)) : null; 1417 } 1418 1419 @Override 1420 public PendingIntent getSessionActivity() { 1421 return MediaControllerCompatApi21.getSessionActivity(mControllerObj); 1422 } 1423 1424 @Override 1425 public void setVolumeTo(int value, int flags) { 1426 MediaControllerCompatApi21.setVolumeTo(mControllerObj, value, flags); 1427 } 1428 1429 @Override 1430 public void adjustVolume(int direction, int flags) { 1431 MediaControllerCompatApi21.adjustVolume(mControllerObj, direction, flags); 1432 } 1433 1434 @Override 1435 public void sendCommand(String command, Bundle params, ResultReceiver cb) { 1436 MediaControllerCompatApi21.sendCommand(mControllerObj, command, params, cb); 1437 } 1438 1439 @Override 1440 public String getPackageName() { 1441 return MediaControllerCompatApi21.getPackageName(mControllerObj); 1442 } 1443 1444 @Override 1445 public Object getMediaController() { 1446 return mControllerObj; 1447 } 1448 1449 // TODO: Handle the case of calling other methods before receiving the extra binder. 1450 private void requestExtraBinder() { 1451 ResultReceiver cb = new ResultReceiver(new Handler()) { 1452 @Override 1453 protected void onReceiveResult(int resultCode, Bundle resultData) { 1454 if (resultData != null) { 1455 mExtraBinder = IMediaSession.Stub.asInterface( 1456 BundleCompat.getBinder( 1457 resultData, MediaSessionCompat.EXTRA_BINDER)); 1458 if (mPendingCallbacks != null) { 1459 for (Callback callback : mPendingCallbacks) { 1460 ExtraCallback extraCallback = new ExtraCallback(callback); 1461 mCallbackMap.put(callback, extraCallback); 1462 callback.mHasExtraCallback = true; 1463 try { 1464 mExtraBinder.registerCallbackListener(extraCallback); 1465 } catch (RemoteException e) { 1466 Log.e(TAG, "Dead object in registerCallback. " + e); 1467 break; 1468 } 1469 } 1470 mPendingCallbacks = null; 1471 } 1472 } 1473 } 1474 }; 1475 sendCommand(COMMAND_GET_EXTRA_BINDER, null, cb); 1476 } 1477 1478 private class ExtraCallback extends IMediaControllerCallback.Stub { 1479 private Callback mCallback; 1480 1481 ExtraCallback(Callback callback) { 1482 mCallback = callback; 1483 } 1484 1485 @Override 1486 public void onEvent(final String event, final Bundle extras) throws RemoteException { 1487 mCallback.mHandler.post(new Runnable() { 1488 @Override 1489 public void run() { 1490 mCallback.onSessionEvent(event, extras); 1491 } 1492 }); 1493 } 1494 1495 @Override 1496 public void onSessionDestroyed() throws RemoteException { 1497 // Will not be called. 1498 throw new AssertionError(); 1499 } 1500 1501 @Override 1502 public void onPlaybackStateChanged(final PlaybackStateCompat state) 1503 throws RemoteException { 1504 mCallback.mHandler.post(new Runnable() { 1505 @Override 1506 public void run() { 1507 mCallback.onPlaybackStateChanged(state); 1508 } 1509 }); 1510 } 1511 1512 @Override 1513 public void onMetadataChanged(MediaMetadataCompat metadata) throws RemoteException { 1514 // Will not be called. 1515 throw new AssertionError(); 1516 } 1517 1518 @Override 1519 public void onQueueChanged(List<QueueItem> queue) throws RemoteException { 1520 // Will not be called. 1521 throw new AssertionError(); 1522 } 1523 1524 @Override 1525 public void onQueueTitleChanged(CharSequence title) throws RemoteException { 1526 // Will not be called. 1527 throw new AssertionError(); 1528 } 1529 1530 @Override 1531 public void onExtrasChanged(Bundle extras) throws RemoteException { 1532 // Will not be called. 1533 throw new AssertionError(); 1534 } 1535 1536 @Override 1537 public void onVolumeInfoChanged(ParcelableVolumeInfo info) throws RemoteException { 1538 // Will not be called. 1539 throw new AssertionError(); 1540 } 1541 } 1542 } 1543 1544 static class TransportControlsApi21 extends TransportControls { 1545 protected final Object mControlsObj; 1546 1547 public TransportControlsApi21(Object controlsObj) { 1548 mControlsObj = controlsObj; 1549 } 1550 1551 @Override 1552 public void prepare() { 1553 sendCustomAction(MediaSessionCompat.ACTION_PREPARE, null); 1554 } 1555 1556 @Override 1557 public void prepareFromMediaId(String mediaId, Bundle extras) { 1558 Bundle bundle = new Bundle(); 1559 bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_MEDIA_ID, mediaId); 1560 bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras); 1561 sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_MEDIA_ID, bundle); 1562 } 1563 1564 @Override 1565 public void prepareFromSearch(String query, Bundle extras) { 1566 Bundle bundle = new Bundle(); 1567 bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_QUERY, query); 1568 bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras); 1569 sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_SEARCH, bundle); 1570 } 1571 1572 @Override 1573 public void prepareFromUri(Uri uri, Bundle extras) { 1574 Bundle bundle = new Bundle(); 1575 bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_URI, uri); 1576 bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras); 1577 sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_URI, bundle); 1578 } 1579 1580 @Override 1581 public void play() { 1582 MediaControllerCompatApi21.TransportControls.play(mControlsObj); 1583 } 1584 1585 @Override 1586 public void pause() { 1587 MediaControllerCompatApi21.TransportControls.pause(mControlsObj); 1588 } 1589 1590 @Override 1591 public void stop() { 1592 MediaControllerCompatApi21.TransportControls.stop(mControlsObj); 1593 } 1594 1595 @Override 1596 public void seekTo(long pos) { 1597 MediaControllerCompatApi21.TransportControls.seekTo(mControlsObj, pos); 1598 } 1599 1600 @Override 1601 public void fastForward() { 1602 MediaControllerCompatApi21.TransportControls.fastForward(mControlsObj); 1603 } 1604 1605 @Override 1606 public void rewind() { 1607 MediaControllerCompatApi21.TransportControls.rewind(mControlsObj); 1608 } 1609 1610 @Override 1611 public void skipToNext() { 1612 MediaControllerCompatApi21.TransportControls.skipToNext(mControlsObj); 1613 } 1614 1615 @Override 1616 public void skipToPrevious() { 1617 MediaControllerCompatApi21.TransportControls.skipToPrevious(mControlsObj); 1618 } 1619 1620 @Override 1621 public void setRating(RatingCompat rating) { 1622 MediaControllerCompatApi21.TransportControls.setRating(mControlsObj, 1623 rating != null ? rating.getRating() : null); 1624 } 1625 1626 @Override 1627 public void playFromMediaId(String mediaId, Bundle extras) { 1628 MediaControllerCompatApi21.TransportControls.playFromMediaId(mControlsObj, mediaId, 1629 extras); 1630 } 1631 1632 @Override 1633 public void playFromSearch(String query, Bundle extras) { 1634 MediaControllerCompatApi21.TransportControls.playFromSearch(mControlsObj, query, 1635 extras); 1636 } 1637 1638 @Override 1639 public void playFromUri(Uri uri, Bundle extras) { 1640 if (uri == null || Uri.EMPTY.equals(uri)) { 1641 throw new IllegalArgumentException( 1642 "You must specify a non-empty Uri for playFromUri."); 1643 } 1644 Bundle bundle = new Bundle(); 1645 bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_URI, uri); 1646 bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras); 1647 sendCustomAction(MediaSessionCompat.ACTION_PLAY_FROM_URI, bundle); 1648 } 1649 1650 @Override 1651 public void skipToQueueItem(long id) { 1652 MediaControllerCompatApi21.TransportControls.skipToQueueItem(mControlsObj, id); 1653 } 1654 1655 @Override 1656 public void sendCustomAction(CustomAction customAction, Bundle args) { 1657 MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj, 1658 customAction.getAction(), args); 1659 } 1660 1661 @Override 1662 public void sendCustomAction(String action, Bundle args) { 1663 MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj, action, 1664 args); 1665 } 1666 } 1667 1668 static class MediaControllerImplApi23 extends MediaControllerImplApi21 { 1669 1670 public MediaControllerImplApi23(Context context, MediaSessionCompat session) { 1671 super(context, session); 1672 } 1673 1674 public MediaControllerImplApi23(Context context, MediaSessionCompat.Token sessionToken) 1675 throws RemoteException { 1676 super(context, sessionToken); 1677 } 1678 1679 @Override 1680 public TransportControls getTransportControls() { 1681 Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj); 1682 return controlsObj != null ? new TransportControlsApi23(controlsObj) : null; 1683 } 1684 } 1685 1686 static class TransportControlsApi23 extends TransportControlsApi21 { 1687 1688 public TransportControlsApi23(Object controlsObj) { 1689 super(controlsObj); 1690 } 1691 1692 @Override 1693 public void playFromUri(Uri uri, Bundle extras) { 1694 MediaControllerCompatApi23.TransportControls.playFromUri(mControlsObj, uri, 1695 extras); 1696 } 1697 } 1698 1699 static class MediaControllerImplApi24 extends MediaControllerImplApi23 { 1700 1701 public MediaControllerImplApi24(Context context, MediaSessionCompat session) { 1702 super(context, session); 1703 } 1704 1705 public MediaControllerImplApi24(Context context, MediaSessionCompat.Token sessionToken) 1706 throws RemoteException { 1707 super(context, sessionToken); 1708 } 1709 1710 @Override 1711 public TransportControls getTransportControls() { 1712 Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj); 1713 return controlsObj != null ? new TransportControlsApi24(controlsObj) : null; 1714 } 1715 } 1716 1717 static class TransportControlsApi24 extends TransportControlsApi23 { 1718 1719 public TransportControlsApi24(Object controlsObj) { 1720 super(controlsObj); 1721 } 1722 1723 @Override 1724 public void prepare() { 1725 MediaControllerCompatApi24.TransportControls.prepare(mControlsObj); 1726 } 1727 1728 @Override 1729 public void prepareFromMediaId(String mediaId, Bundle extras) { 1730 MediaControllerCompatApi24.TransportControls.prepareFromMediaId( 1731 mControlsObj, mediaId, extras); 1732 } 1733 1734 @Override 1735 public void prepareFromSearch(String query, Bundle extras) { 1736 MediaControllerCompatApi24.TransportControls.prepareFromSearch( 1737 mControlsObj, query, extras); 1738 } 1739 1740 @Override 1741 public void prepareFromUri(Uri uri, Bundle extras) { 1742 MediaControllerCompatApi24.TransportControls.prepareFromUri(mControlsObj, uri, extras); 1743 } 1744 } 1745 1746} 1747