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