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