MediaControllerCompat.java revision e6e4fd277b5d364067cbd67865bf91e3e0cd5cb6
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.Activity; 20import android.app.PendingIntent; 21import android.content.Context; 22import android.media.AudioManager; 23import android.net.Uri; 24import android.os.Bundle; 25import android.os.Handler; 26import android.os.IBinder; 27import android.os.Looper; 28import android.os.Message; 29import android.os.RemoteException; 30import android.os.ResultReceiver; 31import android.support.annotation.VisibleForTesting; 32import android.support.v4.app.BundleCompat; 33import android.support.v4.app.SupportActivity; 34import android.support.v4.media.MediaDescriptionCompat; 35import android.support.v4.media.MediaMetadataCompat; 36import android.support.v4.media.RatingCompat; 37import android.support.v4.media.VolumeProviderCompat; 38import android.support.v4.media.session.MediaSessionCompat.QueueItem; 39import android.support.v4.media.session.PlaybackStateCompat.CustomAction; 40import android.text.TextUtils; 41import android.util.Log; 42import android.view.KeyEvent; 43 44import java.lang.ref.WeakReference; 45import java.util.ArrayList; 46import java.util.HashMap; 47import java.util.List; 48 49/** 50 * Allows an app to interact with an ongoing media session. Media buttons and 51 * other commands can be sent to the session. A callback may be registered to 52 * receive updates from the session, such as metadata and play state changes. 53 * <p> 54 * A MediaController can be created if you have a {@link MediaSessionCompat.Token} 55 * from the session owner. 56 * <p> 57 * MediaController objects are thread-safe. 58 * <p> 59 * This is a helper for accessing features in {@link android.media.session.MediaSession} 60 * introduced after API level 4 in a backwards compatible fashion. 61 */ 62public final class MediaControllerCompat { 63 static final String TAG = "MediaControllerCompat"; 64 65 static final String COMMAND_GET_EXTRA_BINDER = 66 "android.support.v4.media.session.command.GET_EXTRA_BINDER"; 67 static final String COMMAND_ADD_QUEUE_ITEM = 68 "android.support.v4.media.session.command.ADD_QUEUE_ITEM"; 69 static final String COMMAND_ADD_QUEUE_ITEM_AT = 70 "android.support.v4.media.session.command.ADD_QUEUE_ITEM_AT"; 71 static final String COMMAND_REMOVE_QUEUE_ITEM = 72 "android.support.v4.media.session.command.REMOVE_QUEUE_ITEM"; 73 static final String COMMAND_REMOVE_QUEUE_ITEM_AT = 74 "android.support.v4.media.session.command.REMOVE_QUEUE_ITEM_AT"; 75 76 static final String COMMAND_ARGUMENT_MEDIA_DESCRIPTION = 77 "android.support.v4.media.session.command.ARGUMENT_MEDIA_DESCRIPTION"; 78 static final String COMMAND_ARGUMENT_INDEX = 79 "android.support.v4.media.session.command.ARGUMENT_INDEX"; 80 81 private static class MediaControllerExtraData extends SupportActivity.ExtraData { 82 private final MediaControllerCompat mMediaController; 83 84 MediaControllerExtraData(MediaControllerCompat mediaController) { 85 mMediaController = mediaController; 86 } 87 88 MediaControllerCompat getMediaController() { 89 return mMediaController; 90 } 91 } 92 93 /** 94 * Sets a {@link MediaControllerCompat} for later retrieval via 95 * {@link #getMediaController()}. 96 * 97 * <p>On API 21 and later, this controller will be tied to the window of the activity and 98 * media key and volume events which are received while the Activity is in the foreground 99 * will be forwarded to the controller and used to invoke transport controls or adjust the 100 * volume. Prior to API 21, the global handling of media key and volume events through an 101 * active {@link android.support.v4.media.session.MediaSessionCompat} and media button receiver 102 * will still be respected.</p> 103 * 104 * @param mediaController The controller for the session which should receive 105 * media keys and volume changes on API 21 and later. 106 * @see #getMediaController() 107 * @see Activity#setMediaController(android.media.session.MediaController) 108 */ 109 public static void setMediaController(Activity activity, 110 MediaControllerCompat mediaController) { 111 if (activity instanceof SupportActivity) { 112 ((SupportActivity) activity).putExtraData( 113 new MediaControllerExtraData(mediaController)); 114 } 115 if (android.os.Build.VERSION.SDK_INT >= 21) { 116 Object controllerObj = null; 117 if (mediaController != null) { 118 Object sessionTokenObj = mediaController.getSessionToken().getToken(); 119 controllerObj = MediaControllerCompatApi21.fromToken(activity, sessionTokenObj); 120 } 121 MediaControllerCompatApi21.setMediaController(activity, controllerObj); 122 } 123 } 124 125 /** 126 * Retrieves the current {@link MediaControllerCompat} for sending media key and volume events. 127 * 128 * @return The controller which should receive events. 129 * @see #setMediaController(Activity,MediaControllerCompat) 130 * @see #getMediaController() 131 */ 132 public static MediaControllerCompat getMediaController(Activity activity) { 133 if (activity instanceof SupportActivity) { 134 MediaControllerExtraData extraData = 135 ((SupportActivity) activity).getExtraData(MediaControllerExtraData.class); 136 return extraData != null ? extraData.getMediaController() : null; 137 } else if (android.os.Build.VERSION.SDK_INT >= 21) { 138 Object controllerObj = MediaControllerCompatApi21.getMediaController(activity); 139 if (controllerObj == null) { 140 return null; 141 } 142 Object sessionTokenObj = MediaControllerCompatApi21.getSessionToken(controllerObj); 143 try { 144 return new MediaControllerCompat(activity, 145 MediaSessionCompat.Token.fromToken(sessionTokenObj)); 146 } catch (RemoteException e) { 147 Log.e(TAG, "Dead object in getMediaController.", e); 148 } 149 } 150 return null; 151 } 152 153 private final MediaControllerImpl mImpl; 154 private final MediaSessionCompat.Token mToken; 155 156 /** 157 * Creates a media controller from a session. 158 * 159 * @param session The session to be controlled. 160 */ 161 public MediaControllerCompat(Context context, MediaSessionCompat session) { 162 if (session == null) { 163 throw new IllegalArgumentException("session must not be null"); 164 } 165 mToken = session.getSessionToken(); 166 167 if (android.os.Build.VERSION.SDK_INT >= 24) { 168 mImpl = new MediaControllerImplApi24(context, session); 169 } else if (android.os.Build.VERSION.SDK_INT >= 23) { 170 mImpl = new MediaControllerImplApi23(context, session); 171 } else if (android.os.Build.VERSION.SDK_INT >= 21) { 172 mImpl = new MediaControllerImplApi21(context, session); 173 } else { 174 mImpl = new MediaControllerImplBase(mToken); 175 } 176 } 177 178 /** 179 * Creates a media controller from a session token which may have 180 * been obtained from another process. 181 * 182 * @param sessionToken The token of the session to be controlled. 183 * @throws RemoteException if the session is not accessible. 184 */ 185 public MediaControllerCompat(Context context, MediaSessionCompat.Token sessionToken) 186 throws RemoteException { 187 if (sessionToken == null) { 188 throw new IllegalArgumentException("sessionToken must not be null"); 189 } 190 mToken = sessionToken; 191 192 if (android.os.Build.VERSION.SDK_INT >= 24) { 193 mImpl = new MediaControllerImplApi24(context, sessionToken); 194 } else if (android.os.Build.VERSION.SDK_INT >= 23) { 195 mImpl = new MediaControllerImplApi23(context, sessionToken); 196 } else if (android.os.Build.VERSION.SDK_INT >= 21) { 197 mImpl = new MediaControllerImplApi21(context, sessionToken); 198 } else { 199 mImpl = new MediaControllerImplBase(mToken); 200 } 201 } 202 203 /** 204 * Get a {@link TransportControls} instance for this session. 205 * 206 * @return A controls instance 207 */ 208 public TransportControls getTransportControls() { 209 return mImpl.getTransportControls(); 210 } 211 212 /** 213 * Send the specified media button event to the session. Only media keys can 214 * be sent by this method, other keys will be ignored. 215 * 216 * @param keyEvent The media button event to dispatch. 217 * @return true if the event was sent to the session, false otherwise. 218 */ 219 public boolean dispatchMediaButtonEvent(KeyEvent keyEvent) { 220 if (keyEvent == null) { 221 throw new IllegalArgumentException("KeyEvent may not be null"); 222 } 223 return mImpl.dispatchMediaButtonEvent(keyEvent); 224 } 225 226 /** 227 * Get the current playback state for this session. 228 * 229 * @return The current PlaybackState or null 230 */ 231 public PlaybackStateCompat getPlaybackState() { 232 return mImpl.getPlaybackState(); 233 } 234 235 /** 236 * Get the current metadata for this session. 237 * 238 * @return The current MediaMetadata or null. 239 */ 240 public MediaMetadataCompat getMetadata() { 241 return mImpl.getMetadata(); 242 } 243 244 /** 245 * Get the current play queue for this session if one is set. If you only 246 * care about the current item {@link #getMetadata()} should be used. 247 * 248 * @return The current play queue or null. 249 */ 250 public List<MediaSessionCompat.QueueItem> getQueue() { 251 return mImpl.getQueue(); 252 } 253 254 /** 255 * Add a queue item from the given {@code description} at the end of the play queue 256 * of this session. Not all sessions may support this. 257 * 258 * @param description The {@link MediaDescriptionCompat} for creating the 259 * {@link MediaSessionCompat.QueueItem} to be inserted. 260 * @throws UnsupportedOperationException If this session doesn't support this. 261 * @see MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS 262 */ 263 public void addQueueItem(MediaDescriptionCompat description) { 264 mImpl.addQueueItem(description); 265 } 266 267 /** 268 * Add a queue item from the given {@code description} at the specified position 269 * in the play queue of this session. Shifts the queue item currently at that position 270 * (if any) and any subsequent queue items to the right (adds one to their indices). 271 * Not all sessions may support this. 272 * 273 * @param description The {@link MediaDescriptionCompat} for creating the 274 * {@link MediaSessionCompat.QueueItem} to be inserted. 275 * @param index The index at which the created {@link MediaSessionCompat.QueueItem} 276 * is to be inserted. 277 * @throws UnsupportedOperationException If this session doesn't support this. 278 * @see MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS 279 */ 280 public void addQueueItem(MediaDescriptionCompat description, int index) { 281 mImpl.addQueueItem(description, index); 282 } 283 284 /** 285 * Remove the first occurrence of the specified {@link MediaSessionCompat.QueueItem} 286 * with the given {@link MediaDescriptionCompat description} in the play queue of the 287 * associated session. Not all sessions may support this. 288 * 289 * @param description The {@link MediaDescriptionCompat} for denoting the 290 * {@link MediaSessionCompat.QueueItem} to be removed. 291 * @throws UnsupportedOperationException If this session doesn't support this. 292 * @see MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS 293 */ 294 public void removeQueueItem(MediaDescriptionCompat description) { 295 mImpl.removeQueueItem(description); 296 } 297 298 /** 299 * Remove an queue item at the specified position in the play queue 300 * of this session. Not all sessions may support this. 301 * 302 * @param index The index of the element to be removed. 303 * @throws UnsupportedOperationException If this session doesn't support this. 304 * @see MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS 305 */ 306 public void removeQueueItemAt(int index) { 307 mImpl.removeQueueItemAt(index); 308 } 309 310 /** 311 * Get the queue title for this session. 312 */ 313 public CharSequence getQueueTitle() { 314 return mImpl.getQueueTitle(); 315 } 316 317 /** 318 * Get the extras for this session. 319 */ 320 public Bundle getExtras() { 321 return mImpl.getExtras(); 322 } 323 324 /** 325 * Get the rating type supported by the session. One of: 326 * <ul> 327 * <li>{@link RatingCompat#RATING_NONE}</li> 328 * <li>{@link RatingCompat#RATING_HEART}</li> 329 * <li>{@link RatingCompat#RATING_THUMB_UP_DOWN}</li> 330 * <li>{@link RatingCompat#RATING_3_STARS}</li> 331 * <li>{@link RatingCompat#RATING_4_STARS}</li> 332 * <li>{@link RatingCompat#RATING_5_STARS}</li> 333 * <li>{@link RatingCompat#RATING_PERCENTAGE}</li> 334 * </ul> 335 * 336 * @return The supported rating type 337 */ 338 public int getRatingType() { 339 return mImpl.getRatingType(); 340 } 341 342 /** 343 * Get the repeat mode for this session. 344 * 345 * @return The latest repeat mode set to the session, or 346 * {@link PlaybackStateCompat#REPEAT_MODE_NONE} if not set. 347 */ 348 public int getRepeatMode() { 349 return mImpl.getRepeatMode(); 350 } 351 352 /** 353 * Return whether the shuffle mode is enabled for this session. 354 * 355 * @return {@code true} if the shuffle mode is enabled, {@code false} if disabled or not set. 356 */ 357 public boolean isShuffleModeEnabled() { 358 return mImpl.isShuffleModeEnabled(); 359 } 360 361 /** 362 * Get the flags for this session. Flags are defined in 363 * {@link MediaSessionCompat}. 364 * 365 * @return The current set of flags for the session. 366 */ 367 public long getFlags() { 368 return mImpl.getFlags(); 369 } 370 371 /** 372 * Get the current playback info for this session. 373 * 374 * @return The current playback info or null. 375 */ 376 public PlaybackInfo getPlaybackInfo() { 377 return mImpl.getPlaybackInfo(); 378 } 379 380 /** 381 * Get an intent for launching UI associated with this session if one 382 * exists. 383 * 384 * @return A {@link PendingIntent} to launch UI or null. 385 */ 386 public PendingIntent getSessionActivity() { 387 return mImpl.getSessionActivity(); 388 } 389 390 /** 391 * Get the token for the session this controller is connected to. 392 * 393 * @return The session's token. 394 */ 395 public MediaSessionCompat.Token getSessionToken() { 396 return mToken; 397 } 398 399 /** 400 * Set the volume of the output this session is playing on. The command will 401 * be ignored if it does not support 402 * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in 403 * {@link AudioManager} may be used to affect the handling. 404 * 405 * @see #getPlaybackInfo() 406 * @param value The value to set it to, between 0 and the reported max. 407 * @param flags Flags from {@link AudioManager} to include with the volume 408 * request. 409 */ 410 public void setVolumeTo(int value, int flags) { 411 mImpl.setVolumeTo(value, flags); 412 } 413 414 /** 415 * Adjust the volume of the output this session is playing on. The direction 416 * must be one of {@link AudioManager#ADJUST_LOWER}, 417 * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}. 418 * The command will be ignored if the session does not support 419 * {@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE} or 420 * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in 421 * {@link AudioManager} may be used to affect the handling. 422 * 423 * @see #getPlaybackInfo() 424 * @param direction The direction to adjust the volume in. 425 * @param flags Any flags to pass with the command. 426 */ 427 public void adjustVolume(int direction, int flags) { 428 mImpl.adjustVolume(direction, flags); 429 } 430 431 /** 432 * Adds a callback to receive updates from the Session. Updates will be 433 * posted on the caller's thread. 434 * 435 * @param callback The callback object, must not be null. 436 */ 437 public void registerCallback(Callback callback) { 438 registerCallback(callback, null); 439 } 440 441 /** 442 * Adds a callback to receive updates from the session. Updates will be 443 * posted on the specified handler's thread. 444 * 445 * @param callback The callback object, must not be null. 446 * @param handler The handler to post updates on. If null the callers thread 447 * will be used. 448 */ 449 public void registerCallback(Callback callback, Handler handler) { 450 if (callback == null) { 451 throw new IllegalArgumentException("callback cannot be null"); 452 } 453 if (handler == null) { 454 handler = new Handler(); 455 } 456 mImpl.registerCallback(callback, handler); 457 } 458 459 /** 460 * Stop receiving updates on the specified callback. If an update has 461 * already been posted you may still receive it after calling this method. 462 * 463 * @param callback The callback to remove 464 */ 465 public void unregisterCallback(Callback callback) { 466 if (callback == null) { 467 throw new IllegalArgumentException("callback cannot be null"); 468 } 469 mImpl.unregisterCallback(callback); 470 } 471 472 /** 473 * Sends a generic command to the session. It is up to the session creator 474 * to decide what commands and parameters they will support. As such, 475 * commands should only be sent to sessions that the controller owns. 476 * 477 * @param command The command to send 478 * @param params Any parameters to include with the command 479 * @param cb The callback to receive the result on 480 */ 481 public void sendCommand(String command, Bundle params, ResultReceiver cb) { 482 if (TextUtils.isEmpty(command)) { 483 throw new IllegalArgumentException("command cannot be null or empty"); 484 } 485 mImpl.sendCommand(command, params, cb); 486 } 487 488 /** 489 * Get the session owner's package name. 490 * 491 * @return The package name of of the session owner. 492 */ 493 public String getPackageName() { 494 return mImpl.getPackageName(); 495 } 496 497 @VisibleForTesting 498 boolean isExtraBinderReady() { 499 if (mImpl instanceof MediaControllerImplApi21) { 500 return ((MediaControllerImplApi21) mImpl).mExtraBinder != null; 501 } else { 502 return false; 503 } 504 } 505 506 /** 507 * Gets the underlying framework 508 * {@link android.media.session.MediaController} object. 509 * <p> 510 * This method is only supported on API 21+. 511 * </p> 512 * 513 * @return The underlying {@link android.media.session.MediaController} 514 * object, or null if none. 515 */ 516 public Object getMediaController() { 517 return mImpl.getMediaController(); 518 } 519 520 /** 521 * Callback for receiving updates on from the session. A Callback can be 522 * registered using {@link #registerCallback} 523 */ 524 public static abstract class Callback implements IBinder.DeathRecipient { 525 private final Object mCallbackObj; 526 MessageHandler mHandler; 527 boolean mHasExtraCallback; 528 529 boolean mRegistered = false; 530 531 public Callback() { 532 if (android.os.Build.VERSION.SDK_INT >= 21) { 533 mCallbackObj = MediaControllerCompatApi21.createCallback(new StubApi21()); 534 } else { 535 mCallbackObj = new StubCompat(); 536 } 537 } 538 539 /** 540 * Override to handle the session being destroyed. The session is no 541 * longer valid after this call and calls to it will be ignored. 542 */ 543 public void onSessionDestroyed() { 544 } 545 546 /** 547 * Override to handle custom events sent by the session owner without a 548 * specified interface. Controllers should only handle these for 549 * sessions they own. 550 * 551 * @param event The event from the session. 552 * @param extras Optional parameters for the event. 553 */ 554 public void onSessionEvent(String event, Bundle extras) { 555 } 556 557 /** 558 * Override to handle changes in playback state. 559 * 560 * @param state The new playback state of the session 561 */ 562 public void onPlaybackStateChanged(PlaybackStateCompat state) { 563 } 564 565 /** 566 * Override to handle changes to the current metadata. 567 * 568 * @param metadata The current metadata for the session or null if none. 569 * @see MediaMetadataCompat 570 */ 571 public void onMetadataChanged(MediaMetadataCompat metadata) { 572 } 573 574 /** 575 * Override to handle changes to items in the queue. 576 * 577 * @see MediaSessionCompat.QueueItem 578 * @param queue A list of items in the current play queue. It should 579 * include the currently playing item as well as previous and 580 * upcoming items if applicable. 581 */ 582 public void onQueueChanged(List<MediaSessionCompat.QueueItem> queue) { 583 } 584 585 /** 586 * Override to handle changes to the queue title. 587 * 588 * @param title The title that should be displayed along with the play 589 * queue such as "Now Playing". May be null if there is no 590 * such title. 591 */ 592 public void onQueueTitleChanged(CharSequence title) { 593 } 594 595 /** 596 * Override to handle chagnes to the {@link MediaSessionCompat} extras. 597 * 598 * @param extras The extras that can include other information 599 * associated with the {@link MediaSessionCompat}. 600 */ 601 public void onExtrasChanged(Bundle extras) { 602 } 603 604 /** 605 * Override to handle changes to the audio info. 606 * 607 * @param info The current audio info for this session. 608 */ 609 public void onAudioInfoChanged(PlaybackInfo info) { 610 } 611 612 /** 613 * Override to handle changes to the repeat mode. 614 * 615 * @param repeatMode The repeat mode. It should be one of followings: 616 * {@link PlaybackStateCompat#REPEAT_MODE_NONE}, 617 * {@link PlaybackStateCompat#REPEAT_MODE_ONE}, 618 * {@link PlaybackStateCompat#REPEAT_MODE_ALL} 619 */ 620 public void onRepeatModeChanged(@PlaybackStateCompat.RepeatMode int repeatMode) { 621 } 622 623 /** 624 * Override to handle changes to the shuffle mode. 625 * 626 * @param enabled {@code true} if the shuffle mode is enabled, {@code false} otherwise. 627 */ 628 public void onShuffleModeChanged(boolean enabled) { 629 } 630 631 @Override 632 public void binderDied() { 633 onSessionDestroyed(); 634 } 635 636 /** 637 * Set the handler to use for pre 21 callbacks. 638 */ 639 private void setHandler(Handler handler) { 640 mHandler = new MessageHandler(handler.getLooper()); 641 } 642 643 private class StubApi21 implements MediaControllerCompatApi21.Callback { 644 StubApi21() { 645 } 646 647 @Override 648 public void onSessionDestroyed() { 649 Callback.this.onSessionDestroyed(); 650 } 651 652 @Override 653 public void onSessionEvent(String event, Bundle extras) { 654 if (mHasExtraCallback && android.os.Build.VERSION.SDK_INT < 23) { 655 // Ignore. ExtraCallback will handle this. 656 } else { 657 Callback.this.onSessionEvent(event, extras); 658 } 659 } 660 661 @Override 662 public void onPlaybackStateChanged(Object stateObj) { 663 if (mHasExtraCallback) { 664 // Ignore. ExtraCallback will handle this. 665 } else { 666 Callback.this.onPlaybackStateChanged( 667 PlaybackStateCompat.fromPlaybackState(stateObj)); 668 } 669 } 670 671 @Override 672 public void onMetadataChanged(Object metadataObj) { 673 Callback.this.onMetadataChanged(MediaMetadataCompat.fromMediaMetadata(metadataObj)); 674 } 675 676 @Override 677 public void onQueueChanged(List<?> queue) { 678 Callback.this.onQueueChanged(QueueItem.fromQueueItemList(queue)); 679 } 680 681 @Override 682 public void onQueueTitleChanged(CharSequence title) { 683 Callback.this.onQueueTitleChanged(title); 684 } 685 686 @Override 687 public void onExtrasChanged(Bundle extras) { 688 Callback.this.onExtrasChanged(extras); 689 } 690 691 @Override 692 public void onAudioInfoChanged( 693 int type, int stream, int control, int max, int current) { 694 Callback.this.onAudioInfoChanged( 695 new PlaybackInfo(type, stream, control, max, current)); 696 } 697 } 698 699 private class StubCompat extends IMediaControllerCallback.Stub { 700 701 StubCompat() { 702 } 703 704 @Override 705 public void onEvent(String event, Bundle extras) throws RemoteException { 706 mHandler.post(MessageHandler.MSG_EVENT, event, extras); 707 } 708 709 @Override 710 public void onSessionDestroyed() throws RemoteException { 711 mHandler.post(MessageHandler.MSG_DESTROYED, null, null); 712 } 713 714 @Override 715 public void onPlaybackStateChanged(PlaybackStateCompat state) throws RemoteException { 716 mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE, state, null); 717 } 718 719 @Override 720 public void onMetadataChanged(MediaMetadataCompat metadata) throws RemoteException { 721 mHandler.post(MessageHandler.MSG_UPDATE_METADATA, metadata, null); 722 } 723 724 @Override 725 public void onQueueChanged(List<QueueItem> queue) throws RemoteException { 726 mHandler.post(MessageHandler.MSG_UPDATE_QUEUE, queue, null); 727 } 728 729 @Override 730 public void onQueueTitleChanged(CharSequence title) throws RemoteException { 731 mHandler.post(MessageHandler.MSG_UPDATE_QUEUE_TITLE, title, null); 732 } 733 734 @Override 735 public void onRepeatModeChanged(int repeatMode) throws RemoteException { 736 mHandler.post(MessageHandler.MSG_UPDATE_REPEAT_MODE, repeatMode, null); 737 } 738 739 @Override 740 public void onShuffleModeChanged(boolean enabled) throws RemoteException { 741 mHandler.post(MessageHandler.MSG_UPDATE_SHUFFLE_MODE, enabled, null); 742 } 743 744 @Override 745 public void onExtrasChanged(Bundle extras) throws RemoteException { 746 mHandler.post(MessageHandler.MSG_UPDATE_EXTRAS, extras, null); 747 } 748 749 @Override 750 public void onVolumeInfoChanged(ParcelableVolumeInfo info) throws RemoteException { 751 PlaybackInfo pi = null; 752 if (info != null) { 753 pi = new PlaybackInfo(info.volumeType, info.audioStream, info.controlType, 754 info.maxVolume, info.currentVolume); 755 } 756 mHandler.post(MessageHandler.MSG_UPDATE_VOLUME, pi, null); 757 } 758 } 759 760 private class MessageHandler extends Handler { 761 private static final int MSG_EVENT = 1; 762 private static final int MSG_UPDATE_PLAYBACK_STATE = 2; 763 private static final int MSG_UPDATE_METADATA = 3; 764 private static final int MSG_UPDATE_VOLUME = 4; 765 private static final int MSG_UPDATE_QUEUE = 5; 766 private static final int MSG_UPDATE_QUEUE_TITLE = 6; 767 private static final int MSG_UPDATE_EXTRAS = 7; 768 private static final int MSG_DESTROYED = 8; 769 private static final int MSG_UPDATE_REPEAT_MODE = 9; 770 private static final int MSG_UPDATE_SHUFFLE_MODE = 10; 771 772 public MessageHandler(Looper looper) { 773 super(looper); 774 } 775 776 @Override 777 public void handleMessage(Message msg) { 778 if (!mRegistered) { 779 return; 780 } 781 switch (msg.what) { 782 case MSG_EVENT: 783 onSessionEvent((String) msg.obj, msg.getData()); 784 break; 785 case MSG_UPDATE_PLAYBACK_STATE: 786 onPlaybackStateChanged((PlaybackStateCompat) msg.obj); 787 break; 788 case MSG_UPDATE_METADATA: 789 onMetadataChanged((MediaMetadataCompat) msg.obj); 790 break; 791 case MSG_UPDATE_QUEUE: 792 onQueueChanged((List<MediaSessionCompat.QueueItem>) msg.obj); 793 break; 794 case MSG_UPDATE_QUEUE_TITLE: 795 onQueueTitleChanged((CharSequence) msg.obj); 796 break; 797 case MSG_UPDATE_REPEAT_MODE: 798 onRepeatModeChanged((int) msg.obj); 799 break; 800 case MSG_UPDATE_SHUFFLE_MODE: 801 onShuffleModeChanged((boolean) msg.obj); 802 break; 803 case MSG_UPDATE_EXTRAS: 804 onExtrasChanged((Bundle) msg.obj); 805 break; 806 case MSG_UPDATE_VOLUME: 807 onAudioInfoChanged((PlaybackInfo) msg.obj); 808 break; 809 case MSG_DESTROYED: 810 onSessionDestroyed(); 811 break; 812 } 813 } 814 815 public void post(int what, Object obj, Bundle data) { 816 Message msg = obtainMessage(what, obj); 817 msg.setData(data); 818 msg.sendToTarget(); 819 } 820 } 821 } 822 823 /** 824 * Interface for controlling media playback on a session. This allows an app 825 * to send media transport commands to the session. 826 */ 827 public static abstract class TransportControls { 828 TransportControls() { 829 } 830 831 /** 832 * Request that the player prepare its playback without audio focus. In other words, other 833 * session can continue to play during the preparation of this session. This method can be 834 * used to speed up the start of the playback. Once the preparation is done, the session 835 * will change its playback state to {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards, 836 * {@link #play} can be called to start playback. If the preparation is not needed, 837 * {@link #play} can be directly called without this method. 838 */ 839 public abstract void prepare(); 840 841 /** 842 * Request that the player prepare playback for a specific media id. In other words, other 843 * session can continue to play during the preparation of this session. This method can be 844 * used to speed up the start of the playback. Once the preparation is 845 * done, the session will change its playback state to 846 * {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards, {@link #play} can be called to 847 * start playback. If the preparation is not needed, {@link #playFromMediaId} can 848 * be directly called without this method. 849 * 850 * @param mediaId The id of the requested media. 851 * @param extras Optional extras that can include extra information about the media item 852 * to be prepared. 853 */ 854 public abstract void prepareFromMediaId(String mediaId, Bundle extras); 855 856 /** 857 * Request that the player prepare playback for a specific search query. 858 * An empty or null query should be treated as a request to prepare any 859 * music. In other words, other session can continue to play during 860 * the preparation of this session. This method can be used to speed up the start of the 861 * playback. Once the preparation is done, the session will change its playback state to 862 * {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards, {@link #play} can be called to 863 * start playback. If the preparation is not needed, {@link #playFromSearch} can be directly 864 * called without this method. 865 * 866 * @param query The search query. 867 * @param extras Optional extras that can include extra information 868 * about the query. 869 */ 870 public abstract void prepareFromSearch(String query, Bundle extras); 871 872 /** 873 * Request that the player prepare playback for a specific {@link Uri}. 874 * In other words, other session can continue to play during the preparation of this 875 * session. This method can be used to speed up the start of the playback. 876 * Once the preparation is done, the session will change its playback state to 877 * {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards, {@link #play} can be called to 878 * start playback. If the preparation is not needed, {@link #playFromUri} can be directly 879 * called without this method. 880 * 881 * @param uri The URI of the requested media. 882 * @param extras Optional extras that can include extra information about the media item 883 * to be prepared. 884 */ 885 public abstract void prepareFromUri(Uri uri, Bundle extras); 886 887 /** 888 * Request that the player start its playback at its current position. 889 */ 890 public abstract void play(); 891 892 /** 893 * Request that the player start playback for a specific {@link Uri}. 894 * 895 * @param mediaId The uri of the requested media. 896 * @param extras Optional extras that can include extra information 897 * about the media item to be played. 898 */ 899 public abstract void playFromMediaId(String mediaId, Bundle extras); 900 901 /** 902 * Request that the player start playback for a specific search query. 903 * An empty or null query should be treated as a request to play any 904 * music. 905 * 906 * @param query The search query. 907 * @param extras Optional extras that can include extra information 908 * about the query. 909 */ 910 public abstract void playFromSearch(String query, Bundle extras); 911 912 /** 913 * Request that the player start playback for a specific {@link Uri}. 914 * 915 * @param uri The URI of the requested media. 916 * @param extras Optional extras that can include extra information about the media item 917 * to be played. 918 */ 919 public abstract void playFromUri(Uri uri, Bundle extras); 920 921 /** 922 * Play an item with a specific id in the play queue. If you specify an 923 * id that is not in the play queue, the behavior is undefined. 924 */ 925 public abstract void skipToQueueItem(long id); 926 927 /** 928 * Request that the player pause its playback and stay at its current 929 * position. 930 */ 931 public abstract void pause(); 932 933 /** 934 * Request that the player stop its playback; it may clear its state in 935 * whatever way is appropriate. 936 */ 937 public abstract void stop(); 938 939 /** 940 * Move to a new location in the media stream. 941 * 942 * @param pos Position to move to, in milliseconds. 943 */ 944 public abstract void seekTo(long pos); 945 946 /** 947 * Start fast forwarding. If playback is already fast forwarding this 948 * may increase the rate. 949 */ 950 public abstract void fastForward(); 951 952 /** 953 * Skip to the next item. 954 */ 955 public abstract void skipToNext(); 956 957 /** 958 * Start rewinding. If playback is already rewinding this may increase 959 * the rate. 960 */ 961 public abstract void rewind(); 962 963 /** 964 * Skip to the previous item. 965 */ 966 public abstract void skipToPrevious(); 967 968 /** 969 * Rate the current content. This will cause the rating to be set for 970 * the current user. The Rating type must match the type returned by 971 * {@link #getRatingType()}. 972 * 973 * @param rating The rating to set for the current content 974 */ 975 public abstract void setRating(RatingCompat rating); 976 977 /** 978 * Set the repeat mode for this session. 979 * 980 * @param repeatMode The repeat mode. Must be one of the followings: 981 * {@link PlaybackStateCompat#REPEAT_MODE_NONE}, 982 * {@link PlaybackStateCompat#REPEAT_MODE_ONE}, 983 * {@link PlaybackStateCompat#REPEAT_MODE_ALL} 984 */ 985 public abstract void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode); 986 987 /** 988 * Set the shuffle mode for this session. 989 * 990 * @param enabled {@code true} to enable the shuffle mode, {@code false} to disable. 991 */ 992 public abstract void setShuffleModeEnabled(boolean enabled); 993 994 /** 995 * Send a custom action for the {@link MediaSessionCompat} to perform. 996 * 997 * @param customAction The action to perform. 998 * @param args Optional arguments to supply to the 999 * {@link MediaSessionCompat} for this custom action. 1000 */ 1001 public abstract void sendCustomAction(PlaybackStateCompat.CustomAction customAction, 1002 Bundle args); 1003 1004 /** 1005 * Send the id and args from a custom action for the 1006 * {@link MediaSessionCompat} to perform. 1007 * 1008 * @see #sendCustomAction(PlaybackStateCompat.CustomAction action, 1009 * Bundle args) 1010 * @param action The action identifier of the 1011 * {@link PlaybackStateCompat.CustomAction} as specified by 1012 * the {@link MediaSessionCompat}. 1013 * @param args Optional arguments to supply to the 1014 * {@link MediaSessionCompat} for this custom action. 1015 */ 1016 public abstract void sendCustomAction(String action, Bundle args); 1017 } 1018 1019 /** 1020 * Holds information about the way volume is handled for this session. 1021 */ 1022 public static final class PlaybackInfo { 1023 /** 1024 * The session uses local playback. 1025 */ 1026 public static final int PLAYBACK_TYPE_LOCAL = 1; 1027 /** 1028 * The session uses remote playback. 1029 */ 1030 public static final int PLAYBACK_TYPE_REMOTE = 2; 1031 1032 private final int mPlaybackType; 1033 // TODO update audio stream with AudioAttributes support version 1034 private final int mAudioStream; 1035 private final int mVolumeControl; 1036 private final int mMaxVolume; 1037 private final int mCurrentVolume; 1038 1039 PlaybackInfo(int type, int stream, int control, int max, int current) { 1040 mPlaybackType = type; 1041 mAudioStream = stream; 1042 mVolumeControl = control; 1043 mMaxVolume = max; 1044 mCurrentVolume = current; 1045 } 1046 1047 /** 1048 * Get the type of volume handling, either local or remote. One of: 1049 * <ul> 1050 * <li>{@link PlaybackInfo#PLAYBACK_TYPE_LOCAL}</li> 1051 * <li>{@link PlaybackInfo#PLAYBACK_TYPE_REMOTE}</li> 1052 * </ul> 1053 * 1054 * @return The type of volume handling this session is using. 1055 */ 1056 public int getPlaybackType() { 1057 return mPlaybackType; 1058 } 1059 1060 /** 1061 * Get the stream this is currently controlling volume on. When the volume 1062 * type is {@link PlaybackInfo#PLAYBACK_TYPE_REMOTE} this value does not 1063 * have meaning and should be ignored. 1064 * 1065 * @return The stream this session is playing on. 1066 */ 1067 public int getAudioStream() { 1068 // TODO switch to AudioAttributesCompat when it is added. 1069 return mAudioStream; 1070 } 1071 1072 /** 1073 * Get the type of volume control that can be used. One of: 1074 * <ul> 1075 * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}</li> 1076 * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE}</li> 1077 * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_FIXED}</li> 1078 * </ul> 1079 * 1080 * @return The type of volume control that may be used with this 1081 * session. 1082 */ 1083 public int getVolumeControl() { 1084 return mVolumeControl; 1085 } 1086 1087 /** 1088 * Get the maximum volume that may be set for this session. 1089 * 1090 * @return The maximum allowed volume where this session is playing. 1091 */ 1092 public int getMaxVolume() { 1093 return mMaxVolume; 1094 } 1095 1096 /** 1097 * Get the current volume for this session. 1098 * 1099 * @return The current volume where this session is playing. 1100 */ 1101 public int getCurrentVolume() { 1102 return mCurrentVolume; 1103 } 1104 } 1105 1106 interface MediaControllerImpl { 1107 void registerCallback(Callback callback, Handler handler); 1108 1109 void unregisterCallback(Callback callback); 1110 boolean dispatchMediaButtonEvent(KeyEvent keyEvent); 1111 TransportControls getTransportControls(); 1112 PlaybackStateCompat getPlaybackState(); 1113 MediaMetadataCompat getMetadata(); 1114 1115 List<MediaSessionCompat.QueueItem> getQueue(); 1116 void addQueueItem(MediaDescriptionCompat description); 1117 void addQueueItem(MediaDescriptionCompat description, int index); 1118 void removeQueueItem(MediaDescriptionCompat description); 1119 void removeQueueItemAt(int index); 1120 CharSequence getQueueTitle(); 1121 Bundle getExtras(); 1122 int getRatingType(); 1123 int getRepeatMode(); 1124 boolean isShuffleModeEnabled(); 1125 long getFlags(); 1126 PlaybackInfo getPlaybackInfo(); 1127 PendingIntent getSessionActivity(); 1128 1129 void setVolumeTo(int value, int flags); 1130 void adjustVolume(int direction, int flags); 1131 void sendCommand(String command, Bundle params, ResultReceiver cb); 1132 1133 String getPackageName(); 1134 Object getMediaController(); 1135 } 1136 1137 static class MediaControllerImplBase implements MediaControllerImpl { 1138 private MediaSessionCompat.Token mToken; 1139 private IMediaSession mBinder; 1140 private TransportControls mTransportControls; 1141 1142 public MediaControllerImplBase(MediaSessionCompat.Token token) { 1143 mToken = token; 1144 mBinder = IMediaSession.Stub.asInterface((IBinder) token.getToken()); 1145 } 1146 1147 @Override 1148 public void registerCallback(Callback callback, Handler handler) { 1149 if (callback == null) { 1150 throw new IllegalArgumentException("callback may not be null."); 1151 } 1152 try { 1153 mBinder.asBinder().linkToDeath(callback, 0); 1154 mBinder.registerCallbackListener((IMediaControllerCallback) callback.mCallbackObj); 1155 callback.setHandler(handler); 1156 callback.mRegistered = true; 1157 } catch (RemoteException e) { 1158 Log.e(TAG, "Dead object in registerCallback.", e); 1159 callback.onSessionDestroyed(); 1160 } 1161 } 1162 1163 @Override 1164 public void unregisterCallback(Callback callback) { 1165 if (callback == null) { 1166 throw new IllegalArgumentException("callback may not be null."); 1167 } 1168 try { 1169 mBinder.unregisterCallbackListener( 1170 (IMediaControllerCallback) callback.mCallbackObj); 1171 mBinder.asBinder().unlinkToDeath(callback, 0); 1172 callback.mRegistered = false; 1173 } catch (RemoteException e) { 1174 Log.e(TAG, "Dead object in unregisterCallback.", e); 1175 } 1176 } 1177 1178 @Override 1179 public boolean dispatchMediaButtonEvent(KeyEvent event) { 1180 if (event == null) { 1181 throw new IllegalArgumentException("event may not be null."); 1182 } 1183 try { 1184 mBinder.sendMediaButton(event); 1185 } catch (RemoteException e) { 1186 Log.e(TAG, "Dead object in dispatchMediaButtonEvent.", e); 1187 } 1188 return false; 1189 } 1190 1191 @Override 1192 public TransportControls getTransportControls() { 1193 if (mTransportControls == null) { 1194 mTransportControls = new TransportControlsBase(mBinder); 1195 } 1196 1197 return mTransportControls; 1198 } 1199 1200 @Override 1201 public PlaybackStateCompat getPlaybackState() { 1202 try { 1203 return mBinder.getPlaybackState(); 1204 } catch (RemoteException e) { 1205 Log.e(TAG, "Dead object in getPlaybackState.", e); 1206 } 1207 return null; 1208 } 1209 1210 @Override 1211 public MediaMetadataCompat getMetadata() { 1212 try { 1213 return mBinder.getMetadata(); 1214 } catch (RemoteException e) { 1215 Log.e(TAG, "Dead object in getMetadata.", e); 1216 } 1217 return null; 1218 } 1219 1220 @Override 1221 public List<MediaSessionCompat.QueueItem> getQueue() { 1222 try { 1223 return mBinder.getQueue(); 1224 } catch (RemoteException e) { 1225 Log.e(TAG, "Dead object in getQueue.", e); 1226 } 1227 return null; 1228 } 1229 1230 @Override 1231 public void addQueueItem(MediaDescriptionCompat description) { 1232 try { 1233 long flags = mBinder.getFlags(); 1234 if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) { 1235 throw new UnsupportedOperationException( 1236 "This session doesn't support queue management operations"); 1237 } 1238 mBinder.addQueueItem(description); 1239 } catch (RemoteException e) { 1240 Log.e(TAG, "Dead object in addQueueItem.", e); 1241 } 1242 } 1243 1244 @Override 1245 public void addQueueItem(MediaDescriptionCompat description, int index) { 1246 try { 1247 long flags = mBinder.getFlags(); 1248 if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) { 1249 throw new UnsupportedOperationException( 1250 "This session doesn't support queue management operations"); 1251 } 1252 mBinder.addQueueItemAt(description, index); 1253 } catch (RemoteException e) { 1254 Log.e(TAG, "Dead object in addQueueItemAt.", e); 1255 } 1256 } 1257 1258 @Override 1259 public void removeQueueItem(MediaDescriptionCompat description) { 1260 try { 1261 long flags = mBinder.getFlags(); 1262 if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) { 1263 throw new UnsupportedOperationException( 1264 "This session doesn't support queue management operations"); 1265 } 1266 mBinder.removeQueueItem(description); 1267 } catch (RemoteException e) { 1268 Log.e(TAG, "Dead object in removeQueueItem.", e); 1269 } 1270 } 1271 1272 @Override 1273 public void removeQueueItemAt(int index) { 1274 try { 1275 long flags = mBinder.getFlags(); 1276 if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) { 1277 throw new UnsupportedOperationException( 1278 "This session doesn't support queue management operations"); 1279 } 1280 mBinder.removeQueueItemAt(index); 1281 } catch (RemoteException e) { 1282 Log.e(TAG, "Dead object in removeQueueItemAt.", e); 1283 } 1284 } 1285 1286 @Override 1287 public CharSequence getQueueTitle() { 1288 try { 1289 return mBinder.getQueueTitle(); 1290 } catch (RemoteException e) { 1291 Log.e(TAG, "Dead object in getQueueTitle.", e); 1292 } 1293 return null; 1294 } 1295 1296 @Override 1297 public Bundle getExtras() { 1298 try { 1299 return mBinder.getExtras(); 1300 } catch (RemoteException e) { 1301 Log.e(TAG, "Dead object in getExtras.", e); 1302 } 1303 return null; 1304 } 1305 1306 @Override 1307 public int getRatingType() { 1308 try { 1309 return mBinder.getRatingType(); 1310 } catch (RemoteException e) { 1311 Log.e(TAG, "Dead object in getRatingType.", e); 1312 } 1313 return 0; 1314 } 1315 1316 @Override 1317 public int getRepeatMode() { 1318 try { 1319 return mBinder.getRepeatMode(); 1320 } catch (RemoteException e) { 1321 Log.e(TAG, "Dead object in getRepeatMode.", e); 1322 } 1323 return 0; 1324 } 1325 1326 @Override 1327 public boolean isShuffleModeEnabled() { 1328 try { 1329 return mBinder.isShuffleModeEnabled(); 1330 } catch (RemoteException e) { 1331 Log.e(TAG, "Dead object in isShuffleModeEnabled.", e); 1332 } 1333 return false; 1334 } 1335 1336 @Override 1337 public long getFlags() { 1338 try { 1339 return mBinder.getFlags(); 1340 } catch (RemoteException e) { 1341 Log.e(TAG, "Dead object in getFlags.", e); 1342 } 1343 return 0; 1344 } 1345 1346 @Override 1347 public PlaybackInfo getPlaybackInfo() { 1348 try { 1349 ParcelableVolumeInfo info = mBinder.getVolumeAttributes(); 1350 PlaybackInfo pi = new PlaybackInfo(info.volumeType, info.audioStream, 1351 info.controlType, info.maxVolume, info.currentVolume); 1352 return pi; 1353 } catch (RemoteException e) { 1354 Log.e(TAG, "Dead object in getPlaybackInfo.", e); 1355 } 1356 return null; 1357 } 1358 1359 @Override 1360 public PendingIntent getSessionActivity() { 1361 try { 1362 return mBinder.getLaunchPendingIntent(); 1363 } catch (RemoteException e) { 1364 Log.e(TAG, "Dead object in getSessionActivity.", e); 1365 } 1366 return null; 1367 } 1368 1369 @Override 1370 public void setVolumeTo(int value, int flags) { 1371 try { 1372 mBinder.setVolumeTo(value, flags, null); 1373 } catch (RemoteException e) { 1374 Log.e(TAG, "Dead object in setVolumeTo.", e); 1375 } 1376 } 1377 1378 @Override 1379 public void adjustVolume(int direction, int flags) { 1380 try { 1381 mBinder.adjustVolume(direction, flags, null); 1382 } catch (RemoteException e) { 1383 Log.e(TAG, "Dead object in adjustVolume.", e); 1384 } 1385 } 1386 1387 @Override 1388 public void sendCommand(String command, Bundle params, ResultReceiver cb) { 1389 try { 1390 mBinder.sendCommand(command, params, 1391 new MediaSessionCompat.ResultReceiverWrapper(cb)); 1392 } catch (RemoteException e) { 1393 Log.e(TAG, "Dead object in sendCommand.", e); 1394 } 1395 } 1396 1397 @Override 1398 public String getPackageName() { 1399 try { 1400 return mBinder.getPackageName(); 1401 } catch (RemoteException e) { 1402 Log.e(TAG, "Dead object in getPackageName.", e); 1403 } 1404 return null; 1405 } 1406 1407 @Override 1408 public Object getMediaController() { 1409 return null; 1410 } 1411 } 1412 1413 static class TransportControlsBase extends TransportControls { 1414 private IMediaSession mBinder; 1415 1416 public TransportControlsBase(IMediaSession binder) { 1417 mBinder = binder; 1418 } 1419 1420 @Override 1421 public void prepare() { 1422 try { 1423 mBinder.prepare(); 1424 } catch (RemoteException e) { 1425 Log.e(TAG, "Dead object in prepare.", e); 1426 } 1427 } 1428 1429 @Override 1430 public void prepareFromMediaId(String mediaId, Bundle extras) { 1431 try { 1432 mBinder.prepareFromMediaId(mediaId, extras); 1433 } catch (RemoteException e) { 1434 Log.e(TAG, "Dead object in prepareFromMediaId.", e); 1435 } 1436 } 1437 1438 @Override 1439 public void prepareFromSearch(String query, Bundle extras) { 1440 try { 1441 mBinder.prepareFromSearch(query, extras); 1442 } catch (RemoteException e) { 1443 Log.e(TAG, "Dead object in prepareFromSearch.", e); 1444 } 1445 } 1446 1447 @Override 1448 public void prepareFromUri(Uri uri, Bundle extras) { 1449 try { 1450 mBinder.prepareFromUri(uri, extras); 1451 } catch (RemoteException e) { 1452 Log.e(TAG, "Dead object in prepareFromUri.", e); 1453 } 1454 } 1455 1456 @Override 1457 public void play() { 1458 try { 1459 mBinder.play(); 1460 } catch (RemoteException e) { 1461 Log.e(TAG, "Dead object in play.", e); 1462 } 1463 } 1464 1465 @Override 1466 public void playFromMediaId(String mediaId, Bundle extras) { 1467 try { 1468 mBinder.playFromMediaId(mediaId, extras); 1469 } catch (RemoteException e) { 1470 Log.e(TAG, "Dead object in playFromMediaId.", e); 1471 } 1472 } 1473 1474 @Override 1475 public void playFromSearch(String query, Bundle extras) { 1476 try { 1477 mBinder.playFromSearch(query, extras); 1478 } catch (RemoteException e) { 1479 Log.e(TAG, "Dead object in playFromSearch.", e); 1480 } 1481 } 1482 1483 @Override 1484 public void playFromUri(Uri uri, Bundle extras) { 1485 try { 1486 mBinder.playFromUri(uri, extras); 1487 } catch (RemoteException e) { 1488 Log.e(TAG, "Dead object in playFromUri.", e); 1489 } 1490 } 1491 1492 @Override 1493 public void skipToQueueItem(long id) { 1494 try { 1495 mBinder.skipToQueueItem(id); 1496 } catch (RemoteException e) { 1497 Log.e(TAG, "Dead object in skipToQueueItem.", e); 1498 } 1499 } 1500 1501 @Override 1502 public void pause() { 1503 try { 1504 mBinder.pause(); 1505 } catch (RemoteException e) { 1506 Log.e(TAG, "Dead object in pause.", e); 1507 } 1508 } 1509 1510 @Override 1511 public void stop() { 1512 try { 1513 mBinder.stop(); 1514 } catch (RemoteException e) { 1515 Log.e(TAG, "Dead object in stop.", e); 1516 } 1517 } 1518 1519 @Override 1520 public void seekTo(long pos) { 1521 try { 1522 mBinder.seekTo(pos); 1523 } catch (RemoteException e) { 1524 Log.e(TAG, "Dead object in seekTo.", e); 1525 } 1526 } 1527 1528 @Override 1529 public void fastForward() { 1530 try { 1531 mBinder.fastForward(); 1532 } catch (RemoteException e) { 1533 Log.e(TAG, "Dead object in fastForward.", e); 1534 } 1535 } 1536 1537 @Override 1538 public void skipToNext() { 1539 try { 1540 mBinder.next(); 1541 } catch (RemoteException e) { 1542 Log.e(TAG, "Dead object in skipToNext.", e); 1543 } 1544 } 1545 1546 @Override 1547 public void rewind() { 1548 try { 1549 mBinder.rewind(); 1550 } catch (RemoteException e) { 1551 Log.e(TAG, "Dead object in rewind.", e); 1552 } 1553 } 1554 1555 @Override 1556 public void skipToPrevious() { 1557 try { 1558 mBinder.previous(); 1559 } catch (RemoteException e) { 1560 Log.e(TAG, "Dead object in skipToPrevious.", e); 1561 } 1562 } 1563 1564 @Override 1565 public void setRating(RatingCompat rating) { 1566 try { 1567 mBinder.rate(rating); 1568 } catch (RemoteException e) { 1569 Log.e(TAG, "Dead object in setRating.", e); 1570 } 1571 } 1572 1573 @Override 1574 public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) { 1575 try { 1576 mBinder.setRepeatMode(repeatMode); 1577 } catch (RemoteException e) { 1578 Log.e(TAG, "Dead object in setRepeatMode.", e); 1579 } 1580 } 1581 1582 @Override 1583 public void setShuffleModeEnabled(boolean enabled) { 1584 try { 1585 mBinder.setShuffleModeEnabled(enabled); 1586 } catch (RemoteException e) { 1587 Log.e(TAG, "Dead object in setShuffleModeEnabled.", e); 1588 } 1589 } 1590 1591 @Override 1592 public void sendCustomAction(CustomAction customAction, Bundle args) { 1593 sendCustomAction(customAction.getAction(), args); 1594 } 1595 1596 @Override 1597 public void sendCustomAction(String action, Bundle args) { 1598 try { 1599 mBinder.sendCustomAction(action, args); 1600 } catch (RemoteException e) { 1601 Log.e(TAG, "Dead object in sendCustomAction.", e); 1602 } 1603 } 1604 } 1605 1606 static class MediaControllerImplApi21 implements MediaControllerImpl { 1607 protected final Object mControllerObj; 1608 1609 // Extra binder is used for applying the framework change of new APIs and bug fixes 1610 // after API 21. 1611 private IMediaSession mExtraBinder; 1612 private HashMap<Callback, ExtraCallback> mCallbackMap = new HashMap<>(); 1613 private List<Callback> mPendingCallbacks = new ArrayList<>(); 1614 1615 public MediaControllerImplApi21(Context context, MediaSessionCompat session) { 1616 mControllerObj = MediaControllerCompatApi21.fromToken(context, 1617 session.getSessionToken().getToken()); 1618 requestExtraBinder(); 1619 } 1620 1621 public MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken) 1622 throws RemoteException { 1623 mControllerObj = MediaControllerCompatApi21.fromToken(context, 1624 sessionToken.getToken()); 1625 if (mControllerObj == null) throw new RemoteException(); 1626 requestExtraBinder(); 1627 } 1628 1629 @Override 1630 public final void registerCallback(Callback callback, Handler handler) { 1631 MediaControllerCompatApi21.registerCallback( 1632 mControllerObj, callback.mCallbackObj, handler); 1633 if (mExtraBinder != null) { 1634 callback.setHandler(handler); 1635 ExtraCallback extraCallback = new ExtraCallback(callback); 1636 mCallbackMap.put(callback, extraCallback); 1637 callback.mHasExtraCallback = true; 1638 try { 1639 mExtraBinder.registerCallbackListener(extraCallback); 1640 } catch (RemoteException e) { 1641 Log.e(TAG, "Dead object in registerCallback.", e); 1642 } 1643 } else { 1644 callback.setHandler(handler); 1645 synchronized (mPendingCallbacks) { 1646 mPendingCallbacks.add(callback); 1647 } 1648 } 1649 } 1650 1651 @Override 1652 public final void unregisterCallback(Callback callback) { 1653 MediaControllerCompatApi21.unregisterCallback(mControllerObj, callback.mCallbackObj); 1654 if (mExtraBinder != null) { 1655 try { 1656 ExtraCallback extraCallback = mCallbackMap.remove(callback); 1657 if (extraCallback != null) { 1658 mExtraBinder.unregisterCallbackListener(extraCallback); 1659 } 1660 } catch (RemoteException e) { 1661 Log.e(TAG, "Dead object in unregisterCallback.", e); 1662 } 1663 } else { 1664 synchronized (mPendingCallbacks) { 1665 mPendingCallbacks.remove(callback); 1666 } 1667 } 1668 } 1669 1670 @Override 1671 public boolean dispatchMediaButtonEvent(KeyEvent event) { 1672 return MediaControllerCompatApi21.dispatchMediaButtonEvent(mControllerObj, event); 1673 } 1674 1675 @Override 1676 public TransportControls getTransportControls() { 1677 Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj); 1678 return controlsObj != null ? new TransportControlsApi21(controlsObj) : null; 1679 } 1680 1681 @Override 1682 public PlaybackStateCompat getPlaybackState() { 1683 if (mExtraBinder != null) { 1684 try { 1685 return mExtraBinder.getPlaybackState(); 1686 } catch (RemoteException e) { 1687 Log.e(TAG, "Dead object in getPlaybackState.", e); 1688 } 1689 } 1690 Object stateObj = MediaControllerCompatApi21.getPlaybackState(mControllerObj); 1691 return stateObj != null ? PlaybackStateCompat.fromPlaybackState(stateObj) : null; 1692 } 1693 1694 @Override 1695 public MediaMetadataCompat getMetadata() { 1696 Object metadataObj = MediaControllerCompatApi21.getMetadata(mControllerObj); 1697 return metadataObj != null ? MediaMetadataCompat.fromMediaMetadata(metadataObj) : null; 1698 } 1699 1700 @Override 1701 public List<MediaSessionCompat.QueueItem> getQueue() { 1702 List<Object> queueObjs = MediaControllerCompatApi21.getQueue(mControllerObj); 1703 return queueObjs != null ? MediaSessionCompat.QueueItem.fromQueueItemList(queueObjs) 1704 : null; 1705 } 1706 1707 @Override 1708 public void addQueueItem(MediaDescriptionCompat description) { 1709 Bundle params = new Bundle(); 1710 params.putParcelable(COMMAND_ARGUMENT_MEDIA_DESCRIPTION, description); 1711 sendCommand(COMMAND_ADD_QUEUE_ITEM, params, null); 1712 } 1713 1714 @Override 1715 public void addQueueItem(MediaDescriptionCompat description, int index) { 1716 Bundle params = new Bundle(); 1717 params.putParcelable(COMMAND_ARGUMENT_MEDIA_DESCRIPTION, description); 1718 params.putInt(COMMAND_ARGUMENT_INDEX, index); 1719 sendCommand(COMMAND_ADD_QUEUE_ITEM_AT, params, null); 1720 } 1721 1722 @Override 1723 public void removeQueueItem(MediaDescriptionCompat description) { 1724 Bundle params = new Bundle(); 1725 params.putParcelable(COMMAND_ARGUMENT_MEDIA_DESCRIPTION, description); 1726 sendCommand(COMMAND_REMOVE_QUEUE_ITEM, params, null); 1727 } 1728 1729 @Override 1730 public void removeQueueItemAt(int index) { 1731 Bundle params = new Bundle(); 1732 params.putInt(COMMAND_ARGUMENT_INDEX, index); 1733 sendCommand(COMMAND_REMOVE_QUEUE_ITEM_AT, params, null); 1734 } 1735 1736 @Override 1737 public CharSequence getQueueTitle() { 1738 return MediaControllerCompatApi21.getQueueTitle(mControllerObj); 1739 } 1740 1741 @Override 1742 public Bundle getExtras() { 1743 return MediaControllerCompatApi21.getExtras(mControllerObj); 1744 } 1745 1746 @Override 1747 public int getRatingType() { 1748 if (android.os.Build.VERSION.SDK_INT < 22 && mExtraBinder != null) { 1749 try { 1750 return mExtraBinder.getRatingType(); 1751 } catch (RemoteException e) { 1752 Log.e(TAG, "Dead object in getRatingType.", e); 1753 } 1754 } 1755 return MediaControllerCompatApi21.getRatingType(mControllerObj); 1756 } 1757 1758 @Override 1759 public int getRepeatMode() { 1760 if (mExtraBinder != null) { 1761 try { 1762 return mExtraBinder.getRepeatMode(); 1763 } catch (RemoteException e) { 1764 Log.e(TAG, "Dead object in getRepeatMode.", e); 1765 } 1766 } 1767 return PlaybackStateCompat.REPEAT_MODE_NONE; 1768 } 1769 1770 @Override 1771 public boolean isShuffleModeEnabled() { 1772 if (mExtraBinder != null) { 1773 try { 1774 return mExtraBinder.isShuffleModeEnabled(); 1775 } catch (RemoteException e) { 1776 Log.e(TAG, "Dead object in isShuffleModeEnabled.", e); 1777 } 1778 } 1779 return false; 1780 } 1781 1782 @Override 1783 public long getFlags() { 1784 return MediaControllerCompatApi21.getFlags(mControllerObj); 1785 } 1786 1787 @Override 1788 public PlaybackInfo getPlaybackInfo() { 1789 Object volumeInfoObj = MediaControllerCompatApi21.getPlaybackInfo(mControllerObj); 1790 return volumeInfoObj != null ? new PlaybackInfo( 1791 MediaControllerCompatApi21.PlaybackInfo.getPlaybackType(volumeInfoObj), 1792 MediaControllerCompatApi21.PlaybackInfo.getLegacyAudioStream(volumeInfoObj), 1793 MediaControllerCompatApi21.PlaybackInfo.getVolumeControl(volumeInfoObj), 1794 MediaControllerCompatApi21.PlaybackInfo.getMaxVolume(volumeInfoObj), 1795 MediaControllerCompatApi21.PlaybackInfo.getCurrentVolume(volumeInfoObj)) : null; 1796 } 1797 1798 @Override 1799 public PendingIntent getSessionActivity() { 1800 return MediaControllerCompatApi21.getSessionActivity(mControllerObj); 1801 } 1802 1803 @Override 1804 public void setVolumeTo(int value, int flags) { 1805 MediaControllerCompatApi21.setVolumeTo(mControllerObj, value, flags); 1806 } 1807 1808 @Override 1809 public void adjustVolume(int direction, int flags) { 1810 MediaControllerCompatApi21.adjustVolume(mControllerObj, direction, flags); 1811 } 1812 1813 @Override 1814 public void sendCommand(String command, Bundle params, ResultReceiver cb) { 1815 MediaControllerCompatApi21.sendCommand(mControllerObj, command, params, cb); 1816 } 1817 1818 @Override 1819 public String getPackageName() { 1820 return MediaControllerCompatApi21.getPackageName(mControllerObj); 1821 } 1822 1823 @Override 1824 public Object getMediaController() { 1825 return mControllerObj; 1826 } 1827 1828 // TODO: Handle the case of calling other methods before receiving the extra binder. 1829 private void requestExtraBinder() { 1830 sendCommand(COMMAND_GET_EXTRA_BINDER, null, 1831 new ExtraBinderRequestResultReceiver(this, new Handler())); 1832 } 1833 1834 private void processPendingCallbacks() { 1835 if (mExtraBinder == null) { 1836 return; 1837 } 1838 synchronized (mPendingCallbacks) { 1839 for (Callback callback : mPendingCallbacks) { 1840 ExtraCallback extraCallback = new ExtraCallback(callback); 1841 mCallbackMap.put(callback, extraCallback); 1842 callback.mHasExtraCallback = true; 1843 try { 1844 mExtraBinder.registerCallbackListener(extraCallback); 1845 } catch (RemoteException e) { 1846 Log.e(TAG, "Dead object in registerCallback.", e); 1847 break; 1848 } 1849 } 1850 mPendingCallbacks.clear(); 1851 } 1852 } 1853 1854 private static class ExtraBinderRequestResultReceiver extends ResultReceiver { 1855 private WeakReference<MediaControllerImplApi21> mMediaControllerImpl; 1856 1857 public ExtraBinderRequestResultReceiver(MediaControllerImplApi21 mediaControllerImpl, 1858 Handler handler) { 1859 super(handler); 1860 mMediaControllerImpl = new WeakReference<>(mediaControllerImpl); 1861 } 1862 1863 @Override 1864 protected void onReceiveResult(int resultCode, Bundle resultData) { 1865 MediaControllerImplApi21 mediaControllerImpl = mMediaControllerImpl.get(); 1866 if (mediaControllerImpl == null || resultData == null) { 1867 return; 1868 } 1869 mediaControllerImpl.mExtraBinder = IMediaSession.Stub.asInterface( 1870 BundleCompat.getBinder(resultData, MediaSessionCompat.EXTRA_BINDER)); 1871 mediaControllerImpl.processPendingCallbacks(); 1872 } 1873 } 1874 1875 private class ExtraCallback extends IMediaControllerCallback.Stub { 1876 private Callback mCallback; 1877 1878 ExtraCallback(Callback callback) { 1879 mCallback = callback; 1880 } 1881 1882 @Override 1883 public void onEvent(final String event, final Bundle extras) throws RemoteException { 1884 mCallback.mHandler.post(new Runnable() { 1885 @Override 1886 public void run() { 1887 mCallback.onSessionEvent(event, extras); 1888 } 1889 }); 1890 } 1891 1892 @Override 1893 public void onSessionDestroyed() throws RemoteException { 1894 // Will not be called. 1895 throw new AssertionError(); 1896 } 1897 1898 @Override 1899 public void onPlaybackStateChanged(final PlaybackStateCompat state) 1900 throws RemoteException { 1901 mCallback.mHandler.post(new Runnable() { 1902 @Override 1903 public void run() { 1904 mCallback.onPlaybackStateChanged(state); 1905 } 1906 }); 1907 } 1908 1909 @Override 1910 public void onMetadataChanged(MediaMetadataCompat metadata) throws RemoteException { 1911 // Will not be called. 1912 throw new AssertionError(); 1913 } 1914 1915 @Override 1916 public void onQueueChanged(List<QueueItem> queue) throws RemoteException { 1917 // Will not be called. 1918 throw new AssertionError(); 1919 } 1920 1921 @Override 1922 public void onQueueTitleChanged(CharSequence title) throws RemoteException { 1923 // Will not be called. 1924 throw new AssertionError(); 1925 } 1926 1927 @Override 1928 public void onRepeatModeChanged(final int repeatMode) throws RemoteException { 1929 mCallback.mHandler.post(new Runnable() { 1930 @Override 1931 public void run() { 1932 mCallback.onRepeatModeChanged(repeatMode); 1933 } 1934 }); 1935 } 1936 1937 @Override 1938 public void onShuffleModeChanged(final boolean enabled) throws RemoteException { 1939 mCallback.mHandler.post(new Runnable() { 1940 @Override 1941 public void run() { 1942 mCallback.onShuffleModeChanged(enabled); 1943 } 1944 }); 1945 } 1946 1947 @Override 1948 public void onExtrasChanged(Bundle extras) throws RemoteException { 1949 // Will not be called. 1950 throw new AssertionError(); 1951 } 1952 1953 @Override 1954 public void onVolumeInfoChanged(ParcelableVolumeInfo info) throws RemoteException { 1955 // Will not be called. 1956 throw new AssertionError(); 1957 } 1958 } 1959 } 1960 1961 static class TransportControlsApi21 extends TransportControls { 1962 protected final Object mControlsObj; 1963 1964 public TransportControlsApi21(Object controlsObj) { 1965 mControlsObj = controlsObj; 1966 } 1967 1968 @Override 1969 public void prepare() { 1970 sendCustomAction(MediaSessionCompat.ACTION_PREPARE, null); 1971 } 1972 1973 @Override 1974 public void prepareFromMediaId(String mediaId, Bundle extras) { 1975 Bundle bundle = new Bundle(); 1976 bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_MEDIA_ID, mediaId); 1977 bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras); 1978 sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_MEDIA_ID, bundle); 1979 } 1980 1981 @Override 1982 public void prepareFromSearch(String query, Bundle extras) { 1983 Bundle bundle = new Bundle(); 1984 bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_QUERY, query); 1985 bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras); 1986 sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_SEARCH, bundle); 1987 } 1988 1989 @Override 1990 public void prepareFromUri(Uri uri, Bundle extras) { 1991 Bundle bundle = new Bundle(); 1992 bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_URI, uri); 1993 bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras); 1994 sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_URI, bundle); 1995 } 1996 1997 @Override 1998 public void play() { 1999 MediaControllerCompatApi21.TransportControls.play(mControlsObj); 2000 } 2001 2002 @Override 2003 public void pause() { 2004 MediaControllerCompatApi21.TransportControls.pause(mControlsObj); 2005 } 2006 2007 @Override 2008 public void stop() { 2009 MediaControllerCompatApi21.TransportControls.stop(mControlsObj); 2010 } 2011 2012 @Override 2013 public void seekTo(long pos) { 2014 MediaControllerCompatApi21.TransportControls.seekTo(mControlsObj, pos); 2015 } 2016 2017 @Override 2018 public void fastForward() { 2019 MediaControllerCompatApi21.TransportControls.fastForward(mControlsObj); 2020 } 2021 2022 @Override 2023 public void rewind() { 2024 MediaControllerCompatApi21.TransportControls.rewind(mControlsObj); 2025 } 2026 2027 @Override 2028 public void skipToNext() { 2029 MediaControllerCompatApi21.TransportControls.skipToNext(mControlsObj); 2030 } 2031 2032 @Override 2033 public void skipToPrevious() { 2034 MediaControllerCompatApi21.TransportControls.skipToPrevious(mControlsObj); 2035 } 2036 2037 @Override 2038 public void setRating(RatingCompat rating) { 2039 MediaControllerCompatApi21.TransportControls.setRating(mControlsObj, 2040 rating != null ? rating.getRating() : null); 2041 } 2042 2043 @Override 2044 public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) { 2045 Bundle bundle = new Bundle(); 2046 bundle.putInt(MediaSessionCompat.ACTION_ARGUMENT_REPEAT_MODE, repeatMode); 2047 sendCustomAction(MediaSessionCompat.ACTION_SET_REPEAT_MODE, bundle); 2048 } 2049 2050 @Override 2051 public void setShuffleModeEnabled(boolean enabled) { 2052 Bundle bundle = new Bundle(); 2053 bundle.putBoolean(MediaSessionCompat.ACTION_ARGUMENT_SHUFFLE_MODE_ENABLED, enabled); 2054 sendCustomAction(MediaSessionCompat.ACTION_SET_SHUFFLE_MODE_ENABLED, bundle); 2055 } 2056 2057 @Override 2058 public void playFromMediaId(String mediaId, Bundle extras) { 2059 MediaControllerCompatApi21.TransportControls.playFromMediaId(mControlsObj, mediaId, 2060 extras); 2061 } 2062 2063 @Override 2064 public void playFromSearch(String query, Bundle extras) { 2065 MediaControllerCompatApi21.TransportControls.playFromSearch(mControlsObj, query, 2066 extras); 2067 } 2068 2069 @Override 2070 public void playFromUri(Uri uri, Bundle extras) { 2071 if (uri == null || Uri.EMPTY.equals(uri)) { 2072 throw new IllegalArgumentException( 2073 "You must specify a non-empty Uri for playFromUri."); 2074 } 2075 Bundle bundle = new Bundle(); 2076 bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_URI, uri); 2077 bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras); 2078 sendCustomAction(MediaSessionCompat.ACTION_PLAY_FROM_URI, bundle); 2079 } 2080 2081 @Override 2082 public void skipToQueueItem(long id) { 2083 MediaControllerCompatApi21.TransportControls.skipToQueueItem(mControlsObj, id); 2084 } 2085 2086 @Override 2087 public void sendCustomAction(CustomAction customAction, Bundle args) { 2088 MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj, 2089 customAction.getAction(), args); 2090 } 2091 2092 @Override 2093 public void sendCustomAction(String action, Bundle args) { 2094 MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj, action, 2095 args); 2096 } 2097 } 2098 2099 static class MediaControllerImplApi23 extends MediaControllerImplApi21 { 2100 2101 public MediaControllerImplApi23(Context context, MediaSessionCompat session) { 2102 super(context, session); 2103 } 2104 2105 public MediaControllerImplApi23(Context context, MediaSessionCompat.Token sessionToken) 2106 throws RemoteException { 2107 super(context, sessionToken); 2108 } 2109 2110 @Override 2111 public TransportControls getTransportControls() { 2112 Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj); 2113 return controlsObj != null ? new TransportControlsApi23(controlsObj) : null; 2114 } 2115 } 2116 2117 static class TransportControlsApi23 extends TransportControlsApi21 { 2118 2119 public TransportControlsApi23(Object controlsObj) { 2120 super(controlsObj); 2121 } 2122 2123 @Override 2124 public void playFromUri(Uri uri, Bundle extras) { 2125 MediaControllerCompatApi23.TransportControls.playFromUri(mControlsObj, uri, 2126 extras); 2127 } 2128 } 2129 2130 static class MediaControllerImplApi24 extends MediaControllerImplApi23 { 2131 2132 public MediaControllerImplApi24(Context context, MediaSessionCompat session) { 2133 super(context, session); 2134 } 2135 2136 public MediaControllerImplApi24(Context context, MediaSessionCompat.Token sessionToken) 2137 throws RemoteException { 2138 super(context, sessionToken); 2139 } 2140 2141 @Override 2142 public TransportControls getTransportControls() { 2143 Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj); 2144 return controlsObj != null ? new TransportControlsApi24(controlsObj) : null; 2145 } 2146 } 2147 2148 static class TransportControlsApi24 extends TransportControlsApi23 { 2149 2150 public TransportControlsApi24(Object controlsObj) { 2151 super(controlsObj); 2152 } 2153 2154 @Override 2155 public void prepare() { 2156 MediaControllerCompatApi24.TransportControls.prepare(mControlsObj); 2157 } 2158 2159 @Override 2160 public void prepareFromMediaId(String mediaId, Bundle extras) { 2161 MediaControllerCompatApi24.TransportControls.prepareFromMediaId( 2162 mControlsObj, mediaId, extras); 2163 } 2164 2165 @Override 2166 public void prepareFromSearch(String query, Bundle extras) { 2167 MediaControllerCompatApi24.TransportControls.prepareFromSearch( 2168 mControlsObj, query, extras); 2169 } 2170 2171 @Override 2172 public void prepareFromUri(Uri uri, Bundle extras) { 2173 MediaControllerCompatApi24.TransportControls.prepareFromUri(mControlsObj, uri, extras); 2174 } 2175 } 2176} 2177