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