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