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