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