MediaControllerCompat.java revision b51f456b92aeb62d5aa9d67e1fb2725b2035fddd
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.media.MediaMetadataCompat; 31import android.support.v4.media.RatingCompat; 32import android.support.v4.media.VolumeProviderCompat; 33import android.support.v4.media.session.MediaSessionCompat.QueueItem; 34import android.support.v4.media.session.PlaybackStateCompat.CustomAction; 35import android.text.TextUtils; 36import android.util.Log; 37import android.view.KeyEvent; 38 39import java.util.ArrayList; 40import java.util.List; 41 42/** 43 * Allows an app to interact with an ongoing media session. Media buttons and 44 * other commands can be sent to the session. A callback may be registered to 45 * receive updates from the session, such as metadata and play state changes. 46 * <p> 47 * A MediaController can be created if you have a {@link MediaSessionCompat.Token} 48 * from the session owner. 49 * <p> 50 * MediaController objects are thread-safe. 51 * <p> 52 * This is a helper for accessing features in {@link android.media.session.MediaSession} 53 * introduced after API level 4 in a backwards compatible fashion. 54 */ 55public final class MediaControllerCompat { 56 private static final String TAG = "MediaControllerCompat"; 57 58 private final MediaControllerImpl mImpl; 59 private final MediaSessionCompat.Token mToken; 60 61 /** 62 * Creates a media controller from a session. 63 * 64 * @param session The session to be controlled. 65 */ 66 public MediaControllerCompat(Context context, MediaSessionCompat session) { 67 if (session == null) { 68 throw new IllegalArgumentException("session must not be null"); 69 } 70 mToken = session.getSessionToken(); 71 72 if (android.os.Build.VERSION.SDK_INT >= 23) { 73 mImpl = new MediaControllerImplApi23(context, session); 74 } else if (android.os.Build.VERSION.SDK_INT >= 21) { 75 mImpl = new MediaControllerImplApi21(context, session); 76 } else { 77 mImpl = new MediaControllerImplBase(mToken); 78 } 79 } 80 81 /** 82 * Creates a media controller from a session token which may have 83 * been obtained from another process. 84 * 85 * @param sessionToken The token of the session to be controlled. 86 * @throws RemoteException if the session is not accessible. 87 */ 88 public MediaControllerCompat(Context context, MediaSessionCompat.Token sessionToken) 89 throws RemoteException { 90 if (sessionToken == null) { 91 throw new IllegalArgumentException("sessionToken must not be null"); 92 } 93 mToken = sessionToken; 94 95 if (android.os.Build.VERSION.SDK_INT >= 21) { 96 mImpl = new MediaControllerImplApi21(context, sessionToken); 97 } else { 98 mImpl = new MediaControllerImplBase(mToken); 99 } 100 } 101 102 /** 103 * Get a {@link TransportControls} instance for this session. 104 * 105 * @return A controls instance 106 */ 107 public TransportControls getTransportControls() { 108 return mImpl.getTransportControls(); 109 } 110 111 /** 112 * Send the specified media button event to the session. Only media keys can 113 * be sent by this method, other keys will be ignored. 114 * 115 * @param keyEvent The media button event to dispatch. 116 * @return true if the event was sent to the session, false otherwise. 117 */ 118 public boolean dispatchMediaButtonEvent(KeyEvent keyEvent) { 119 if (keyEvent == null) { 120 throw new IllegalArgumentException("KeyEvent may not be null"); 121 } 122 return mImpl.dispatchMediaButtonEvent(keyEvent); 123 } 124 125 /** 126 * Get the current playback state for this session. 127 * 128 * @return The current PlaybackState or null 129 */ 130 public PlaybackStateCompat getPlaybackState() { 131 return mImpl.getPlaybackState(); 132 } 133 134 /** 135 * Get the current metadata for this session. 136 * 137 * @return The current MediaMetadata or null. 138 */ 139 public MediaMetadataCompat getMetadata() { 140 return mImpl.getMetadata(); 141 } 142 143 /** 144 * Get the current play queue for this session if one is set. If you only 145 * care about the current item {@link #getMetadata()} should be used. 146 * 147 * @return The current play queue or null. 148 */ 149 public List<MediaSessionCompat.QueueItem> getQueue() { 150 return mImpl.getQueue(); 151 } 152 153 /** 154 * Get the queue title for this session. 155 */ 156 public CharSequence getQueueTitle() { 157 return mImpl.getQueueTitle(); 158 } 159 160 /** 161 * Get the extras for this session. 162 */ 163 public Bundle getExtras() { 164 return mImpl.getExtras(); 165 } 166 167 /** 168 * Get the rating type supported by the session. One of: 169 * <ul> 170 * <li>{@link RatingCompat#RATING_NONE}</li> 171 * <li>{@link RatingCompat#RATING_HEART}</li> 172 * <li>{@link RatingCompat#RATING_THUMB_UP_DOWN}</li> 173 * <li>{@link RatingCompat#RATING_3_STARS}</li> 174 * <li>{@link RatingCompat#RATING_4_STARS}</li> 175 * <li>{@link RatingCompat#RATING_5_STARS}</li> 176 * <li>{@link RatingCompat#RATING_PERCENTAGE}</li> 177 * </ul> 178 * 179 * @return The supported rating type 180 */ 181 public int getRatingType() { 182 return mImpl.getRatingType(); 183 } 184 185 /** 186 * Get the flags for this session. Flags are defined in 187 * {@link MediaSessionCompat}. 188 * 189 * @return The current set of flags for the session. 190 */ 191 public long getFlags() { 192 return mImpl.getFlags(); 193 } 194 195 /** 196 * Get the current playback info for this session. 197 * 198 * @return The current playback info or null. 199 */ 200 public PlaybackInfo getPlaybackInfo() { 201 return mImpl.getPlaybackInfo(); 202 } 203 204 /** 205 * Get an intent for launching UI associated with this session if one 206 * exists. 207 * 208 * @return A {@link PendingIntent} to launch UI or null. 209 */ 210 public PendingIntent getSessionActivity() { 211 return mImpl.getSessionActivity(); 212 } 213 214 /** 215 * Get the token for the session this controller is connected to. 216 * 217 * @return The session's token. 218 */ 219 public MediaSessionCompat.Token getSessionToken() { 220 return mToken; 221 } 222 223 /** 224 * Set the volume of the output this session is playing on. The command will 225 * be ignored if it does not support 226 * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in 227 * {@link AudioManager} may be used to affect the handling. 228 * 229 * @see #getPlaybackInfo() 230 * @param value The value to set it to, between 0 and the reported max. 231 * @param flags Flags from {@link AudioManager} to include with the volume 232 * request. 233 */ 234 public void setVolumeTo(int value, int flags) { 235 mImpl.setVolumeTo(value, flags); 236 } 237 238 /** 239 * Adjust the volume of the output this session is playing on. The direction 240 * must be one of {@link AudioManager#ADJUST_LOWER}, 241 * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}. 242 * The command will be ignored if the session does not support 243 * {@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE} or 244 * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in 245 * {@link AudioManager} may be used to affect the handling. 246 * 247 * @see #getPlaybackInfo() 248 * @param direction The direction to adjust the volume in. 249 * @param flags Any flags to pass with the command. 250 */ 251 public void adjustVolume(int direction, int flags) { 252 mImpl.adjustVolume(direction, flags); 253 } 254 255 /** 256 * Adds a callback to receive updates from the Session. Updates will be 257 * posted on the caller's thread. 258 * 259 * @param callback The callback object, must not be null. 260 */ 261 public void registerCallback(Callback callback) { 262 registerCallback(callback, null); 263 } 264 265 /** 266 * Adds a callback to receive updates from the session. Updates will be 267 * posted on the specified handler's thread. 268 * 269 * @param callback The callback object, must not be null. 270 * @param handler The handler to post updates on. If null the callers thread 271 * will be used. 272 */ 273 public void registerCallback(Callback callback, Handler handler) { 274 if (callback == null) { 275 throw new IllegalArgumentException("callback cannot be null"); 276 } 277 if (handler == null) { 278 handler = new Handler(); 279 } 280 mImpl.registerCallback(callback, handler); 281 } 282 283 /** 284 * Stop receiving updates on the specified callback. If an update has 285 * already been posted you may still receive it after calling this method. 286 * 287 * @param callback The callback to remove 288 */ 289 public void unregisterCallback(Callback callback) { 290 if (callback == null) { 291 throw new IllegalArgumentException("callback cannot be null"); 292 } 293 mImpl.unregisterCallback(callback); 294 } 295 296 /** 297 * Sends a generic command to the session. It is up to the session creator 298 * to decide what commands and parameters they will support. As such, 299 * commands should only be sent to sessions that the controller owns. 300 * 301 * @param command The command to send 302 * @param params Any parameters to include with the command 303 * @param cb The callback to receive the result on 304 */ 305 public void sendCommand(String command, Bundle params, ResultReceiver cb) { 306 if (TextUtils.isEmpty(command)) { 307 throw new IllegalArgumentException("command cannot be null or empty"); 308 } 309 mImpl.sendCommand(command, params, cb); 310 } 311 312 /** 313 * Get the session owner's package name. 314 * 315 * @return The package name of of the session owner. 316 */ 317 public String getPackageName() { 318 return mImpl.getPackageName(); 319 } 320 321 /** 322 * Gets the underlying framework 323 * {@link android.media.session.MediaController} object. 324 * <p> 325 * This method is only supported on API 21+. 326 * </p> 327 * 328 * @return The underlying {@link android.media.session.MediaController} 329 * object, or null if none. 330 */ 331 public Object getMediaController() { 332 return mImpl.getMediaController(); 333 } 334 335 /** 336 * Callback for receiving updates on from the session. A Callback can be 337 * registered using {@link #registerCallback} 338 */ 339 public static abstract class Callback implements IBinder.DeathRecipient { 340 private final Object mCallbackObj; 341 private MessageHandler mHandler; 342 343 private boolean mRegistered = false; 344 345 public Callback() { 346 if (android.os.Build.VERSION.SDK_INT >= 21) { 347 mCallbackObj = MediaControllerCompatApi21.createCallback(new StubApi21()); 348 } else { 349 mCallbackObj = new StubCompat(); 350 } 351 } 352 353 /** 354 * Override to handle the session being destroyed. The session is no 355 * longer valid after this call and calls to it will be ignored. 356 */ 357 public void onSessionDestroyed() { 358 } 359 360 /** 361 * Override to handle custom events sent by the session owner without a 362 * specified interface. Controllers should only handle these for 363 * sessions they own. 364 * 365 * @param event The event from the session. 366 * @param extras Optional parameters for the event. 367 */ 368 public void onSessionEvent(String event, Bundle extras) { 369 } 370 371 /** 372 * Override to handle changes in playback state. 373 * 374 * @param state The new playback state of the session 375 */ 376 public void onPlaybackStateChanged(PlaybackStateCompat state) { 377 } 378 379 /** 380 * Override to handle changes to the current metadata. 381 * 382 * @param metadata The current metadata for the session or null if none. 383 * @see MediaMetadata 384 */ 385 public void onMetadataChanged(MediaMetadataCompat metadata) { 386 } 387 388 /** 389 * Override to handle changes to items in the queue. 390 * 391 * @see MediaSessionCompat.QueueItem 392 * @param queue A list of items in the current play queue. It should 393 * include the currently playing item as well as previous and 394 * upcoming items if applicable. 395 */ 396 public void onQueueChanged(List<MediaSessionCompat.QueueItem> queue) { 397 } 398 399 /** 400 * Override to handle changes to the queue title. 401 * 402 * @param title The title that should be displayed along with the play 403 * queue such as "Now Playing". May be null if there is no 404 * such title. 405 */ 406 public void onQueueTitleChanged(CharSequence title) { 407 } 408 409 /** 410 * Override to handle chagnes to the {@link MediaSessionCompat} extras. 411 * 412 * @param extras The extras that can include other information 413 * associated with the {@link MediaSessionCompat}. 414 */ 415 public void onExtrasChanged(Bundle extras) { 416 } 417 418 /** 419 * Override to handle changes to the audio info. 420 * 421 * @param info The current audio info for this session. 422 */ 423 public void onAudioInfoChanged(PlaybackInfo info) { 424 } 425 426 @Override 427 public void binderDied() { 428 onSessionDestroyed(); 429 } 430 431 /** 432 * Set the handler to use for pre 21 callbacks. 433 */ 434 private void setHandler(Handler handler) { 435 mHandler = new MessageHandler(handler.getLooper()); 436 } 437 438 private class StubApi21 implements MediaControllerCompatApi21.Callback { 439 @Override 440 public void onSessionDestroyed() { 441 Callback.this.onSessionDestroyed(); 442 } 443 444 @Override 445 public void onSessionEvent(String event, Bundle extras) { 446 Callback.this.onSessionEvent(event, extras); 447 } 448 449 @Override 450 public void onPlaybackStateChanged(Object stateObj) { 451 Callback.this.onPlaybackStateChanged( 452 PlaybackStateCompat.fromPlaybackState(stateObj)); 453 } 454 455 @Override 456 public void onMetadataChanged(Object metadataObj) { 457 Callback.this.onMetadataChanged( 458 MediaMetadataCompat.fromMediaMetadata(metadataObj)); 459 } 460 } 461 462 private class StubCompat extends IMediaControllerCallback.Stub { 463 464 @Override 465 public void onEvent(String event, Bundle extras) throws RemoteException { 466 mHandler.post(MessageHandler.MSG_EVENT, event, extras); 467 } 468 469 @Override 470 public void onSessionDestroyed() throws RemoteException { 471 mHandler.post(MessageHandler.MSG_DESTROYED, null, null); 472 } 473 474 @Override 475 public void onPlaybackStateChanged(PlaybackStateCompat state) throws RemoteException { 476 mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE, state, null); 477 } 478 479 @Override 480 public void onMetadataChanged(MediaMetadataCompat metadata) throws RemoteException { 481 mHandler.post(MessageHandler.MSG_UPDATE_METADATA, metadata, null); 482 } 483 484 @Override 485 public void onQueueChanged(List<QueueItem> queue) throws RemoteException { 486 mHandler.post(MessageHandler.MSG_UPDATE_QUEUE, queue, null); 487 } 488 489 @Override 490 public void onQueueTitleChanged(CharSequence title) throws RemoteException { 491 mHandler.post(MessageHandler.MSG_UPDATE_QUEUE_TITLE, title, null); 492 } 493 494 @Override 495 public void onExtrasChanged(Bundle extras) throws RemoteException { 496 mHandler.post(MessageHandler.MSG_UPDATE_EXTRAS, extras, null); 497 } 498 499 @Override 500 public void onVolumeInfoChanged(ParcelableVolumeInfo info) throws RemoteException { 501 PlaybackInfo pi = null; 502 if (info != null) { 503 pi = new PlaybackInfo(info.volumeType, info.audioStream, info.controlType, 504 info.maxVolume, info.currentVolume); 505 } 506 mHandler.post(MessageHandler.MSG_UPDATE_VOLUME, pi, null); 507 } 508 } 509 510 private class MessageHandler extends Handler { 511 private static final int MSG_EVENT = 1; 512 private static final int MSG_UPDATE_PLAYBACK_STATE = 2; 513 private static final int MSG_UPDATE_METADATA = 3; 514 private static final int MSG_UPDATE_VOLUME = 4; 515 private static final int MSG_UPDATE_QUEUE = 5; 516 private static final int MSG_UPDATE_QUEUE_TITLE = 6; 517 private static final int MSG_UPDATE_EXTRAS = 7; 518 private static final int MSG_DESTROYED = 8; 519 520 public MessageHandler(Looper looper) { 521 super(looper); 522 } 523 524 @Override 525 public void handleMessage(Message msg) { 526 if (!mRegistered) { 527 return; 528 } 529 switch (msg.what) { 530 case MSG_EVENT: 531 onSessionEvent((String) msg.obj, msg.getData()); 532 break; 533 case MSG_UPDATE_PLAYBACK_STATE: 534 onPlaybackStateChanged((PlaybackStateCompat) msg.obj); 535 break; 536 case MSG_UPDATE_METADATA: 537 onMetadataChanged((MediaMetadataCompat) msg.obj); 538 break; 539 case MSG_UPDATE_QUEUE: 540 onQueueChanged((List<MediaSessionCompat.QueueItem>) msg.obj); 541 break; 542 case MSG_UPDATE_QUEUE_TITLE: 543 onQueueTitleChanged((CharSequence) msg.obj); 544 break; 545 case MSG_UPDATE_EXTRAS: 546 onExtrasChanged((Bundle) msg.obj); 547 break; 548 case MSG_UPDATE_VOLUME: 549 onAudioInfoChanged((PlaybackInfo) msg.obj); 550 break; 551 case MSG_DESTROYED: 552 onSessionDestroyed(); 553 break; 554 } 555 } 556 557 public void post(int what, Object obj, Bundle data) { 558 obtainMessage(what, obj).sendToTarget(); 559 } 560 } 561 } 562 563 /** 564 * Interface for controlling media playback on a session. This allows an app 565 * to send media transport commands to the session. 566 */ 567 public static abstract class TransportControls { 568 TransportControls() { 569 } 570 571 /** 572 * Request that the player start its playback at its current position. 573 */ 574 public abstract void play(); 575 576 /** 577 * Request that the player start playback for a specific {@link Uri}. 578 * 579 * @param mediaId The uri of the requested media. 580 * @param extras Optional extras that can include extra information 581 * about the media item to be played. 582 */ 583 public abstract void playFromMediaId(String mediaId, Bundle extras); 584 585 /** 586 * Request that the player start playback for a specific search query. 587 * An empty or null query should be treated as a request to play any 588 * music. 589 * 590 * @param query The search query. 591 * @param extras Optional extras that can include extra information 592 * about the query. 593 */ 594 public abstract void playFromSearch(String query, Bundle extras); 595 596 /** 597 * Request that the player start playback for a specific {@link Uri}. 598 * 599 * @param uri The URI of the requested media. 600 * @param extras Optional extras that can include extra information about the media item 601 * to be played. 602 */ 603 public abstract void playFromUri(Uri uri, Bundle extras); 604 605 /** 606 * Play an item with a specific id in the play queue. If you specify an 607 * id that is not in the play queue, the behavior is undefined. 608 */ 609 public abstract void skipToQueueItem(long id); 610 611 /** 612 * Request that the player pause its playback and stay at its current 613 * position. 614 */ 615 public abstract void pause(); 616 617 /** 618 * Request that the player stop its playback; it may clear its state in 619 * whatever way is appropriate. 620 */ 621 public abstract void stop(); 622 623 /** 624 * Move to a new location in the media stream. 625 * 626 * @param pos Position to move to, in milliseconds. 627 */ 628 public abstract void seekTo(long pos); 629 630 /** 631 * Start fast forwarding. If playback is already fast forwarding this 632 * may increase the rate. 633 */ 634 public abstract void fastForward(); 635 636 /** 637 * Skip to the next item. 638 */ 639 public abstract void skipToNext(); 640 641 /** 642 * Start rewinding. If playback is already rewinding this may increase 643 * the rate. 644 */ 645 public abstract void rewind(); 646 647 /** 648 * Skip to the previous item. 649 */ 650 public abstract void skipToPrevious(); 651 652 /** 653 * Rate the current content. This will cause the rating to be set for 654 * the current user. The Rating type must match the type returned by 655 * {@link #getRatingType()}. 656 * 657 * @param rating The rating to set for the current content 658 */ 659 public abstract void setRating(RatingCompat rating); 660 661 /** 662 * Send a custom action for the {@link MediaSessionCompat} to perform. 663 * 664 * @param customAction The action to perform. 665 * @param args Optional arguments to supply to the 666 * {@link MediaSessionCompat} for this custom action. 667 */ 668 public abstract void sendCustomAction(PlaybackStateCompat.CustomAction customAction, 669 Bundle args); 670 671 /** 672 * Send the id and args from a custom action for the 673 * {@link MediaSessionCompat} to perform. 674 * 675 * @see #sendCustomAction(PlaybackStateCompat.CustomAction action, 676 * Bundle args) 677 * @param action The action identifier of the 678 * {@link PlaybackStateCompat.CustomAction} as specified by 679 * the {@link MediaSessionCompat}. 680 * @param args Optional arguments to supply to the 681 * {@link MediaSessionCompat} for this custom action. 682 */ 683 public abstract void sendCustomAction(String action, Bundle args); 684 } 685 686 /** 687 * Holds information about the way volume is handled for this session. 688 */ 689 public static final class PlaybackInfo { 690 /** 691 * The session uses local playback. 692 */ 693 public static final int PLAYBACK_TYPE_LOCAL = 1; 694 /** 695 * The session uses remote playback. 696 */ 697 public static final int PLAYBACK_TYPE_REMOTE = 2; 698 699 private final int mPlaybackType; 700 // TODO update audio stream with AudioAttributes support version 701 private final int mAudioStream; 702 private final int mVolumeControl; 703 private final int mMaxVolume; 704 private final int mCurrentVolume; 705 706 PlaybackInfo(int type, int stream, int control, int max, int current) { 707 mPlaybackType = type; 708 mAudioStream = stream; 709 mVolumeControl = control; 710 mMaxVolume = max; 711 mCurrentVolume = current; 712 } 713 714 /** 715 * Get the type of volume handling, either local or remote. One of: 716 * <ul> 717 * <li>{@link PlaybackInfo#PLAYBACK_TYPE_LOCAL}</li> 718 * <li>{@link PlaybackInfo#PLAYBACK_TYPE_REMOTE}</li> 719 * </ul> 720 * 721 * @return The type of volume handling this session is using. 722 */ 723 public int getPlaybackType() { 724 return mPlaybackType; 725 } 726 727 /** 728 * Get the stream this is currently controlling volume on. When the volume 729 * type is {@link PlaybackInfo#PLAYBACK_TYPE_REMOTE} this value does not 730 * have meaning and should be ignored. 731 * 732 * @return The stream this session is playing on. 733 */ 734 public int getAudioStream() { 735 // TODO switch to AudioAttributesCompat when it is added. 736 return mAudioStream; 737 } 738 739 /** 740 * Get the type of volume control that can be used. One of: 741 * <ul> 742 * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}</li> 743 * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE}</li> 744 * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_FIXED}</li> 745 * </ul> 746 * 747 * @return The type of volume control that may be used with this 748 * session. 749 */ 750 public int getVolumeControl() { 751 return mVolumeControl; 752 } 753 754 /** 755 * Get the maximum volume that may be set for this session. 756 * 757 * @return The maximum allowed volume where this session is playing. 758 */ 759 public int getMaxVolume() { 760 return mMaxVolume; 761 } 762 763 /** 764 * Get the current volume for this session. 765 * 766 * @return The current volume where this session is playing. 767 */ 768 public int getCurrentVolume() { 769 return mCurrentVolume; 770 } 771 } 772 773 interface MediaControllerImpl { 774 void registerCallback(Callback callback, Handler handler); 775 776 void unregisterCallback(Callback callback); 777 boolean dispatchMediaButtonEvent(KeyEvent keyEvent); 778 TransportControls getTransportControls(); 779 PlaybackStateCompat getPlaybackState(); 780 MediaMetadataCompat getMetadata(); 781 782 List<MediaSessionCompat.QueueItem> getQueue(); 783 CharSequence getQueueTitle(); 784 Bundle getExtras(); 785 int getRatingType(); 786 long getFlags(); 787 PlaybackInfo getPlaybackInfo(); 788 PendingIntent getSessionActivity(); 789 790 void setVolumeTo(int value, int flags); 791 void adjustVolume(int direction, int flags); 792 void sendCommand(String command, Bundle params, ResultReceiver cb); 793 794 String getPackageName(); 795 Object getMediaController(); 796 } 797 798 static class MediaControllerImplBase implements MediaControllerImpl { 799 private MediaSessionCompat.Token mToken; 800 private IMediaSession mBinder; 801 private TransportControls mTransportControls; 802 803 public MediaControllerImplBase(MediaSessionCompat.Token token) { 804 mToken = token; 805 mBinder = IMediaSession.Stub.asInterface((IBinder) token.getToken()); 806 } 807 808 @Override 809 public void registerCallback(Callback callback, Handler handler) { 810 if (callback == null) { 811 throw new IllegalArgumentException("callback may not be null."); 812 } 813 try { 814 mBinder.asBinder().linkToDeath(callback, 0); 815 mBinder.registerCallbackListener((IMediaControllerCallback) callback.mCallbackObj); 816 callback.setHandler(handler); 817 callback.mRegistered = true; 818 } catch (RemoteException e) { 819 Log.e(TAG, "Dead object in registerCallback. " + e); 820 callback.onSessionDestroyed(); 821 } 822 } 823 824 @Override 825 public void unregisterCallback(Callback callback) { 826 if (callback == null) { 827 throw new IllegalArgumentException("callback may not be null."); 828 } 829 try { 830 mBinder.unregisterCallbackListener( 831 (IMediaControllerCallback) callback.mCallbackObj); 832 mBinder.asBinder().unlinkToDeath(callback, 0); 833 callback.mRegistered = false; 834 } catch (RemoteException e) { 835 Log.e(TAG, "Dead object in unregisterCallback. " + e); 836 } 837 } 838 839 @Override 840 public boolean dispatchMediaButtonEvent(KeyEvent event) { 841 if (event == null) { 842 throw new IllegalArgumentException("event may not be null."); 843 } 844 try { 845 mBinder.sendMediaButton(event); 846 } catch (RemoteException e) { 847 Log.e(TAG, "Dead object in dispatchMediaButtonEvent. " + e); 848 } 849 return false; 850 } 851 852 @Override 853 public TransportControls getTransportControls() { 854 if (mTransportControls == null) { 855 mTransportControls = new TransportControlsBase(mBinder); 856 } 857 858 return mTransportControls; 859 } 860 861 @Override 862 public PlaybackStateCompat getPlaybackState() { 863 try { 864 return mBinder.getPlaybackState(); 865 } catch (RemoteException e) { 866 Log.e(TAG, "Dead object in getPlaybackState. " + e); 867 } 868 return null; 869 } 870 871 @Override 872 public MediaMetadataCompat getMetadata() { 873 try { 874 return mBinder.getMetadata(); 875 } catch (RemoteException e) { 876 Log.e(TAG, "Dead object in getMetadata. " + e); 877 } 878 return null; 879 } 880 881 @Override 882 public List<MediaSessionCompat.QueueItem> getQueue() { 883 try { 884 return mBinder.getQueue(); 885 } catch (RemoteException e) { 886 Log.e(TAG, "Dead object in getQueue. " + e); 887 } 888 return null; 889 } 890 891 @Override 892 public CharSequence getQueueTitle() { 893 try { 894 return mBinder.getQueueTitle(); 895 } catch (RemoteException e) { 896 Log.e(TAG, "Dead object in getQueueTitle. " + e); 897 } 898 return null; 899 } 900 901 @Override 902 public Bundle getExtras() { 903 try { 904 return mBinder.getExtras(); 905 } catch (RemoteException e) { 906 Log.e(TAG, "Dead object in getExtras. " + e); 907 } 908 return null; 909 } 910 911 @Override 912 public int getRatingType() { 913 try { 914 return mBinder.getRatingType(); 915 } catch (RemoteException e) { 916 Log.e(TAG, "Dead object in getRatingType. " + e); 917 } 918 return 0; 919 } 920 921 @Override 922 public long getFlags() { 923 try { 924 return mBinder.getFlags(); 925 } catch (RemoteException e) { 926 Log.e(TAG, "Dead object in getFlags. " + e); 927 } 928 return 0; 929 } 930 931 @Override 932 public PlaybackInfo getPlaybackInfo() { 933 try { 934 ParcelableVolumeInfo info = mBinder.getVolumeAttributes(); 935 PlaybackInfo pi = new PlaybackInfo(info.volumeType, info.audioStream, 936 info.controlType, info.maxVolume, info.currentVolume); 937 return pi; 938 } catch (RemoteException e) { 939 Log.e(TAG, "Dead object in getPlaybackInfo. " + e); 940 } 941 return null; 942 } 943 944 @Override 945 public PendingIntent getSessionActivity() { 946 try { 947 return mBinder.getLaunchPendingIntent(); 948 } catch (RemoteException e) { 949 Log.e(TAG, "Dead object in getSessionActivity. " + e); 950 } 951 return null; 952 } 953 954 @Override 955 public void setVolumeTo(int value, int flags) { 956 try { 957 mBinder.setVolumeTo(value, flags, null); 958 } catch (RemoteException e) { 959 Log.e(TAG, "Dead object in setVolumeTo. " + e); 960 } 961 } 962 963 @Override 964 public void adjustVolume(int direction, int flags) { 965 try { 966 mBinder.adjustVolume(direction, flags, null); 967 } catch (RemoteException e) { 968 Log.e(TAG, "Dead object in adjustVolume. " + e); 969 } 970 } 971 972 @Override 973 public void sendCommand(String command, Bundle params, ResultReceiver cb) { 974 try { 975 mBinder.sendCommand(command, params, 976 new MediaSessionCompat.ResultReceiverWrapper(cb)); 977 } catch (RemoteException e) { 978 Log.e(TAG, "Dead object in sendCommand. " + e); 979 } 980 } 981 982 @Override 983 public String getPackageName() { 984 try { 985 return mBinder.getPackageName(); 986 } catch (RemoteException e) { 987 Log.e(TAG, "Dead object in getPackageName. " + e); 988 } 989 return null; 990 } 991 992 @Override 993 public Object getMediaController() { 994 return null; 995 } 996 } 997 998 static class TransportControlsBase extends TransportControls { 999 private IMediaSession mBinder; 1000 1001 public TransportControlsBase(IMediaSession binder) { 1002 mBinder = binder; 1003 } 1004 1005 @Override 1006 public void play() { 1007 try { 1008 mBinder.play(); 1009 } catch (RemoteException e) { 1010 Log.e(TAG, "Dead object in play. " + e); 1011 } 1012 } 1013 1014 @Override 1015 public void playFromMediaId(String mediaId, Bundle extras) { 1016 try { 1017 mBinder.playFromMediaId(mediaId, extras); 1018 } catch (RemoteException e) { 1019 Log.e(TAG, "Dead object in playFromMediaId. " + e); 1020 } 1021 } 1022 1023 @Override 1024 public void playFromSearch(String query, Bundle extras) { 1025 try { 1026 mBinder.playFromSearch(query, extras); 1027 } catch (RemoteException e) { 1028 Log.e(TAG, "Dead object in playFromSearch. " + e); 1029 } 1030 } 1031 1032 @Override 1033 public void playFromUri(Uri uri, Bundle extras) { 1034 try { 1035 mBinder.playFromUri(uri, extras); 1036 } catch (RemoteException e) { 1037 Log.e(TAG, "Dead object in playFromUri. " + e); 1038 } 1039 } 1040 1041 @Override 1042 public void skipToQueueItem(long id) { 1043 try { 1044 mBinder.skipToQueueItem(id); 1045 } catch (RemoteException e) { 1046 Log.e(TAG, "Dead object in skipToQueueItem. " + e); 1047 } 1048 } 1049 1050 @Override 1051 public void pause() { 1052 try { 1053 mBinder.pause(); 1054 } catch (RemoteException e) { 1055 Log.e(TAG, "Dead object in pause. " + e); 1056 } 1057 } 1058 1059 @Override 1060 public void stop() { 1061 try { 1062 mBinder.stop(); 1063 } catch (RemoteException e) { 1064 Log.e(TAG, "Dead object in stop. " + e); 1065 } 1066 } 1067 1068 @Override 1069 public void seekTo(long pos) { 1070 try { 1071 mBinder.seekTo(pos); 1072 } catch (RemoteException e) { 1073 Log.e(TAG, "Dead object in seekTo. " + e); 1074 } 1075 } 1076 1077 @Override 1078 public void fastForward() { 1079 try { 1080 mBinder.fastForward(); 1081 } catch (RemoteException e) { 1082 Log.e(TAG, "Dead object in fastForward. " + e); 1083 } 1084 } 1085 1086 @Override 1087 public void skipToNext() { 1088 try { 1089 mBinder.next(); 1090 } catch (RemoteException e) { 1091 Log.e(TAG, "Dead object in skipToNext. " + e); 1092 } 1093 } 1094 1095 @Override 1096 public void rewind() { 1097 try { 1098 mBinder.rewind(); 1099 } catch (RemoteException e) { 1100 Log.e(TAG, "Dead object in rewind. " + e); 1101 } 1102 } 1103 1104 @Override 1105 public void skipToPrevious() { 1106 try { 1107 mBinder.previous(); 1108 } catch (RemoteException e) { 1109 Log.e(TAG, "Dead object in skipToPrevious. " + e); 1110 } 1111 } 1112 1113 @Override 1114 public void setRating(RatingCompat rating) { 1115 try { 1116 mBinder.rate(rating); 1117 } catch (RemoteException e) { 1118 Log.e(TAG, "Dead object in setRating. " + e); 1119 } 1120 } 1121 1122 @Override 1123 public void sendCustomAction(CustomAction customAction, Bundle args) { 1124 sendCustomAction(customAction.getAction(), args); 1125 } 1126 1127 @Override 1128 public void sendCustomAction(String action, Bundle args) { 1129 try { 1130 mBinder.sendCustomAction(action, args); 1131 } catch (RemoteException e) { 1132 Log.e(TAG, "Dead object in sendCustomAction. " + e); 1133 } 1134 } 1135 } 1136 1137 static class MediaControllerImplApi21 implements MediaControllerImpl { 1138 protected final Object mControllerObj; 1139 1140 public MediaControllerImplApi21(Context context, MediaSessionCompat session) { 1141 mControllerObj = MediaControllerCompatApi21.fromToken(context, 1142 session.getSessionToken().getToken()); 1143 } 1144 1145 public MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken) 1146 throws RemoteException { 1147 mControllerObj = MediaControllerCompatApi21.fromToken(context, 1148 sessionToken.getToken()); 1149 if (mControllerObj == null) throw new RemoteException(); 1150 } 1151 1152 @Override 1153 public void registerCallback(Callback callback, Handler handler) { 1154 MediaControllerCompatApi21.registerCallback(mControllerObj, callback.mCallbackObj, handler); 1155 } 1156 1157 @Override 1158 public void unregisterCallback(Callback callback) { 1159 MediaControllerCompatApi21.unregisterCallback(mControllerObj, callback.mCallbackObj); 1160 } 1161 1162 @Override 1163 public boolean dispatchMediaButtonEvent(KeyEvent event) { 1164 return MediaControllerCompatApi21.dispatchMediaButtonEvent(mControllerObj, event); 1165 } 1166 1167 @Override 1168 public TransportControls getTransportControls() { 1169 Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj); 1170 return controlsObj != null ? new TransportControlsApi21(controlsObj) : null; 1171 } 1172 1173 @Override 1174 public PlaybackStateCompat getPlaybackState() { 1175 Object stateObj = MediaControllerCompatApi21.getPlaybackState(mControllerObj); 1176 return stateObj != null ? PlaybackStateCompat.fromPlaybackState(stateObj) : null; 1177 } 1178 1179 @Override 1180 public MediaMetadataCompat getMetadata() { 1181 Object metadataObj = MediaControllerCompatApi21.getMetadata(mControllerObj); 1182 return metadataObj != null ? MediaMetadataCompat.fromMediaMetadata(metadataObj) : null; 1183 } 1184 1185 @Override 1186 public List<MediaSessionCompat.QueueItem> getQueue() { 1187 List<Object> queueObjs = MediaControllerCompatApi21.getQueue(mControllerObj); 1188 if (queueObjs == null) { 1189 return null; 1190 } 1191 List<MediaSessionCompat.QueueItem> queue = 1192 new ArrayList<MediaSessionCompat.QueueItem>(); 1193 for (Object item : queueObjs) { 1194 queue.add(MediaSessionCompat.QueueItem.obtain(item)); 1195 } 1196 return queue; 1197 } 1198 1199 @Override 1200 public CharSequence getQueueTitle() { 1201 return MediaControllerCompatApi21.getQueueTitle(mControllerObj); 1202 } 1203 1204 @Override 1205 public Bundle getExtras() { 1206 return MediaControllerCompatApi21.getExtras(mControllerObj); 1207 } 1208 1209 @Override 1210 public int getRatingType() { 1211 return MediaControllerCompatApi21.getRatingType(mControllerObj); 1212 } 1213 1214 @Override 1215 public long getFlags() { 1216 return MediaControllerCompatApi21.getFlags(mControllerObj); 1217 } 1218 1219 @Override 1220 public PlaybackInfo getPlaybackInfo() { 1221 Object volumeInfoObj = MediaControllerCompatApi21.getPlaybackInfo(mControllerObj); 1222 return volumeInfoObj != null ? new PlaybackInfo( 1223 MediaControllerCompatApi21.PlaybackInfo.getPlaybackType(volumeInfoObj), 1224 MediaControllerCompatApi21.PlaybackInfo.getLegacyAudioStream(volumeInfoObj), 1225 MediaControllerCompatApi21.PlaybackInfo.getVolumeControl(volumeInfoObj), 1226 MediaControllerCompatApi21.PlaybackInfo.getMaxVolume(volumeInfoObj), 1227 MediaControllerCompatApi21.PlaybackInfo.getCurrentVolume(volumeInfoObj)) : null; 1228 } 1229 1230 @Override 1231 public PendingIntent getSessionActivity() { 1232 return MediaControllerCompatApi21.getSessionActivity(mControllerObj); 1233 } 1234 1235 @Override 1236 public void setVolumeTo(int value, int flags) { 1237 MediaControllerCompatApi21.setVolumeTo(mControllerObj, value, flags); 1238 } 1239 1240 @Override 1241 public void adjustVolume(int direction, int flags) { 1242 MediaControllerCompatApi21.adjustVolume(mControllerObj, direction, flags); 1243 } 1244 1245 @Override 1246 public void sendCommand(String command, Bundle params, ResultReceiver cb) { 1247 MediaControllerCompatApi21.sendCommand(mControllerObj, command, params, cb); 1248 } 1249 1250 @Override 1251 public String getPackageName() { 1252 return MediaControllerCompatApi21.getPackageName(mControllerObj); 1253 } 1254 1255 @Override 1256 public Object getMediaController() { 1257 return mControllerObj; 1258 } 1259 } 1260 1261 static class TransportControlsApi21 extends TransportControls { 1262 protected final Object mControlsObj; 1263 1264 public TransportControlsApi21(Object controlsObj) { 1265 mControlsObj = controlsObj; 1266 } 1267 1268 @Override 1269 public void play() { 1270 MediaControllerCompatApi21.TransportControls.play(mControlsObj); 1271 } 1272 1273 @Override 1274 public void pause() { 1275 MediaControllerCompatApi21.TransportControls.pause(mControlsObj); 1276 } 1277 1278 @Override 1279 public void stop() { 1280 MediaControllerCompatApi21.TransportControls.stop(mControlsObj); 1281 } 1282 1283 @Override 1284 public void seekTo(long pos) { 1285 MediaControllerCompatApi21.TransportControls.seekTo(mControlsObj, pos); 1286 } 1287 1288 @Override 1289 public void fastForward() { 1290 MediaControllerCompatApi21.TransportControls.fastForward(mControlsObj); 1291 } 1292 1293 @Override 1294 public void rewind() { 1295 MediaControllerCompatApi21.TransportControls.rewind(mControlsObj); 1296 } 1297 1298 @Override 1299 public void skipToNext() { 1300 MediaControllerCompatApi21.TransportControls.skipToNext(mControlsObj); 1301 } 1302 1303 @Override 1304 public void skipToPrevious() { 1305 MediaControllerCompatApi21.TransportControls.skipToPrevious(mControlsObj); 1306 } 1307 1308 @Override 1309 public void setRating(RatingCompat rating) { 1310 MediaControllerCompatApi21.TransportControls.setRating(mControlsObj, 1311 rating != null ? rating.getRating() : null); 1312 } 1313 1314 @Override 1315 public void playFromMediaId(String mediaId, Bundle extras) { 1316 MediaControllerCompatApi21.TransportControls.playFromMediaId(mControlsObj, mediaId, 1317 extras); 1318 } 1319 1320 @Override 1321 public void playFromSearch(String query, Bundle extras) { 1322 MediaControllerCompatApi21.TransportControls.playFromSearch(mControlsObj, query, 1323 extras); 1324 } 1325 1326 @Override 1327 public void playFromUri(Uri uri, Bundle extras) { 1328 } 1329 1330 @Override 1331 public void skipToQueueItem(long id) { 1332 MediaControllerCompatApi21.TransportControls.skipToQueueItem(mControlsObj, id); 1333 } 1334 1335 @Override 1336 public void sendCustomAction(CustomAction customAction, Bundle args) { 1337 MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj, 1338 customAction.getAction(), args); 1339 } 1340 1341 @Override 1342 public void sendCustomAction(String action, Bundle args) { 1343 MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj, action, 1344 args); 1345 } 1346 } 1347 1348 static class MediaControllerImplApi23 extends MediaControllerImplApi21 { 1349 1350 public MediaControllerImplApi23(Context context, MediaSessionCompat session) { 1351 super(context, session); 1352 } 1353 1354 public MediaControllerImplApi23(Context context, MediaSessionCompat.Token sessionToken) 1355 throws RemoteException { 1356 super(context, sessionToken); 1357 } 1358 1359 @Override 1360 public TransportControls getTransportControls() { 1361 Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj); 1362 return controlsObj != null ? new TransportControlsApi23(controlsObj) : null; 1363 } 1364 } 1365 1366 static class TransportControlsApi23 extends TransportControlsApi21 { 1367 1368 public TransportControlsApi23(Object controlsObj) { 1369 super(controlsObj); 1370 } 1371 1372 @Override 1373 public void playFromUri(Uri uri, Bundle extras) { 1374 MediaControllerCompatApi23.TransportControls.playFromUri(mControlsObj, uri, 1375 extras); 1376 } 1377 } 1378} 1379