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