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