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