MediaSession.java revision 3625bf72cb8bcf3c7f8f8cd8d708d7206824cc62
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.media.session; 18 19import android.annotation.IntDef; 20import android.annotation.NonNull; 21import android.annotation.Nullable; 22import android.app.Activity; 23import android.app.PendingIntent; 24import android.content.Context; 25import android.content.Intent; 26import android.content.pm.ParceledListSlice; 27import android.media.AudioAttributes; 28import android.media.MediaDescription; 29import android.media.MediaMetadata; 30import android.media.Rating; 31import android.media.VolumeProvider; 32import android.net.Uri; 33import android.os.Bundle; 34import android.os.Handler; 35import android.os.Looper; 36import android.os.Message; 37import android.os.Parcel; 38import android.os.Parcelable; 39import android.os.RemoteException; 40import android.os.ResultReceiver; 41import android.os.UserHandle; 42import android.service.media.MediaBrowserService; 43import android.text.TextUtils; 44import android.util.Log; 45import android.view.KeyEvent; 46 47import java.lang.annotation.Retention; 48import java.lang.annotation.RetentionPolicy; 49import java.lang.ref.WeakReference; 50import java.util.List; 51 52/** 53 * Allows interaction with media controllers, volume keys, media buttons, and 54 * transport controls. 55 * <p> 56 * A MediaSession should be created when an app wants to publish media playback 57 * information or handle media keys. In general an app only needs one session 58 * for all playback, though multiple sessions can be created to provide finer 59 * grain controls of media. 60 * <p> 61 * Once a session is created the owner of the session may pass its 62 * {@link #getSessionToken() session token} to other processes to allow them to 63 * create a {@link MediaController} to interact with the session. 64 * <p> 65 * To receive commands, media keys, and other events a {@link Callback} must be 66 * set with {@link #setCallback(Callback)} and {@link #setActive(boolean) 67 * setActive(true)} must be called. 68 * <p> 69 * When an app is finished performing playback it must call {@link #release()} 70 * to clean up the session and notify any controllers. 71 * <p> 72 * MediaSession objects are thread safe. 73 */ 74public final class MediaSession { 75 private static final String TAG = "MediaSession"; 76 77 /** 78 * Set this flag on the session to indicate that it can handle media button 79 * events. 80 */ 81 public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0; 82 83 /** 84 * Set this flag on the session to indicate that it handles transport 85 * control commands through its {@link Callback}. 86 */ 87 public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1; 88 89 /** 90 * System only flag for a session that needs to have priority over all other 91 * sessions. This flag ensures this session will receive media button events 92 * regardless of the current ordering in the system. 93 * 94 * @hide 95 */ 96 public static final int FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 1 << 16; 97 98 /** @hide */ 99 @Retention(RetentionPolicy.SOURCE) 100 @IntDef(flag = true, value = { 101 FLAG_HANDLES_MEDIA_BUTTONS, 102 FLAG_HANDLES_TRANSPORT_CONTROLS, 103 FLAG_EXCLUSIVE_GLOBAL_PRIORITY }) 104 public @interface SessionFlags { } 105 106 private final Object mLock = new Object(); 107 108 private final MediaSession.Token mSessionToken; 109 private final MediaController mController; 110 private final ISession mBinder; 111 private final CallbackStub mCbStub; 112 113 private CallbackMessageHandler mCallback; 114 private VolumeProvider mVolumeProvider; 115 private PlaybackState mPlaybackState; 116 117 private boolean mActive = false; 118 119 /** 120 * Creates a new session. The session will automatically be registered with 121 * the system but will not be published until {@link #setActive(boolean) 122 * setActive(true)} is called. You must call {@link #release()} when 123 * finished with the session. 124 * 125 * @param context The context to use to create the session. 126 * @param tag A short name for debugging purposes. 127 */ 128 public MediaSession(@NonNull Context context, @NonNull String tag) { 129 this(context, tag, UserHandle.myUserId()); 130 } 131 132 /** 133 * Creates a new session as the specified user. To create a session as a 134 * user other than your own you must hold the 135 * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} 136 * permission. 137 * 138 * @param context The context to use to create the session. 139 * @param tag A short name for debugging purposes. 140 * @param userId The user id to create the session as. 141 * @hide 142 */ 143 public MediaSession(@NonNull Context context, @NonNull String tag, int userId) { 144 if (context == null) { 145 throw new IllegalArgumentException("context cannot be null."); 146 } 147 if (TextUtils.isEmpty(tag)) { 148 throw new IllegalArgumentException("tag cannot be null or empty"); 149 } 150 mCbStub = new CallbackStub(this); 151 MediaSessionManager manager = (MediaSessionManager) context 152 .getSystemService(Context.MEDIA_SESSION_SERVICE); 153 try { 154 mBinder = manager.createSession(mCbStub, tag, userId); 155 mSessionToken = new Token(mBinder.getController()); 156 mController = new MediaController(context, mSessionToken); 157 } catch (RemoteException e) { 158 throw new RuntimeException("Remote error creating session.", e); 159 } 160 } 161 162 /** 163 * Set the callback to receive updates for the MediaSession. This includes 164 * media button events and transport controls. The caller's thread will be 165 * used to post updates. 166 * <p> 167 * Set the callback to null to stop receiving updates. 168 * 169 * @param callback The callback object 170 */ 171 public void setCallback(@Nullable Callback callback) { 172 setCallback(callback, null); 173 } 174 175 /** 176 * Set the callback to receive updates for the MediaSession. This includes 177 * media button events and transport controls. 178 * <p> 179 * Set the callback to null to stop receiving updates. 180 * 181 * @param callback The callback to receive updates on. 182 * @param handler The handler that events should be posted on. 183 */ 184 public void setCallback(@Nullable Callback callback, @Nullable Handler handler) { 185 if (callback == null) { 186 mCallback = null; 187 return; 188 } 189 synchronized (mLock) { 190 if (mCallback != null && mCallback.mCallback == callback) { 191 Log.w(TAG, "Tried to set same callback, ignoring"); 192 return; 193 } 194 if (handler == null) { 195 handler = new Handler(); 196 } 197 CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(), 198 callback); 199 mCallback = msgHandler; 200 } 201 } 202 203 /** 204 * Set an intent for launching UI for this Session. This can be used as a 205 * quick link to an ongoing media screen. The intent should be for an 206 * activity that may be started using {@link Activity#startActivity(Intent)}. 207 * 208 * @param pi The intent to launch to show UI for this Session. 209 */ 210 public void setSessionActivity(@Nullable PendingIntent pi) { 211 try { 212 mBinder.setLaunchPendingIntent(pi); 213 } catch (RemoteException e) { 214 Log.wtf(TAG, "Failure in setLaunchPendingIntent.", e); 215 } 216 } 217 218 /** 219 * Set a pending intent for your media button receiver to allow restarting 220 * playback after the session has been stopped. If your app is started in 221 * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via 222 * the pending intent. 223 * 224 * @param mbr The {@link PendingIntent} to send the media button event to. 225 */ 226 public void setMediaButtonReceiver(@Nullable PendingIntent mbr) { 227 try { 228 mBinder.setMediaButtonReceiver(mbr); 229 } catch (RemoteException e) { 230 Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e); 231 } 232 } 233 234 /** 235 * Set any flags for the session. 236 * 237 * @param flags The flags to set for this session. 238 */ 239 public void setFlags(@SessionFlags int flags) { 240 try { 241 mBinder.setFlags(flags); 242 } catch (RemoteException e) { 243 Log.wtf(TAG, "Failure in setFlags.", e); 244 } 245 } 246 247 /** 248 * Set the attributes for this session's audio. This will affect the 249 * system's volume handling for this session. If 250 * {@link #setPlaybackToRemote} was previously called it will stop receiving 251 * volume commands and the system will begin sending volume changes to the 252 * appropriate stream. 253 * <p> 254 * By default sessions use attributes for media. 255 * 256 * @param attributes The {@link AudioAttributes} for this session's audio. 257 */ 258 public void setPlaybackToLocal(AudioAttributes attributes) { 259 if (attributes == null) { 260 throw new IllegalArgumentException("Attributes cannot be null for local playback."); 261 } 262 try { 263 mBinder.setPlaybackToLocal(attributes); 264 } catch (RemoteException e) { 265 Log.wtf(TAG, "Failure in setPlaybackToLocal.", e); 266 } 267 } 268 269 /** 270 * Configure this session to use remote volume handling. This must be called 271 * to receive volume button events, otherwise the system will adjust the 272 * appropriate stream volume for this session. If 273 * {@link #setPlaybackToLocal} was previously called the system will stop 274 * handling volume changes for this session and pass them to the volume 275 * provider instead. 276 * 277 * @param volumeProvider The provider that will handle volume changes. May 278 * not be null. 279 */ 280 public void setPlaybackToRemote(@NonNull VolumeProvider volumeProvider) { 281 if (volumeProvider == null) { 282 throw new IllegalArgumentException("volumeProvider may not be null!"); 283 } 284 mVolumeProvider = volumeProvider; 285 volumeProvider.setCallback(new VolumeProvider.Callback() { 286 @Override 287 public void onVolumeChanged(VolumeProvider volumeProvider) { 288 notifyRemoteVolumeChanged(volumeProvider); 289 } 290 }); 291 292 try { 293 mBinder.setPlaybackToRemote(volumeProvider.getVolumeControl(), 294 volumeProvider.getMaxVolume()); 295 mBinder.setCurrentVolume(volumeProvider.onGetCurrentVolume()); 296 } catch (RemoteException e) { 297 Log.wtf(TAG, "Failure in setPlaybackToRemote.", e); 298 } 299 } 300 301 /** 302 * Set if this session is currently active and ready to receive commands. If 303 * set to false your session's controller may not be discoverable. You must 304 * set the session to active before it can start receiving media button 305 * events or transport commands. 306 * 307 * @param active Whether this session is active or not. 308 */ 309 public void setActive(boolean active) { 310 if (mActive == active) { 311 return; 312 } 313 try { 314 mBinder.setActive(active); 315 mActive = active; 316 } catch (RemoteException e) { 317 Log.wtf(TAG, "Failure in setActive.", e); 318 } 319 } 320 321 /** 322 * Get the current active state of this session. 323 * 324 * @return True if the session is active, false otherwise. 325 */ 326 public boolean isActive() { 327 return mActive; 328 } 329 330 /** 331 * Send a proprietary event to all MediaControllers listening to this 332 * Session. It's up to the Controller/Session owner to determine the meaning 333 * of any events. 334 * 335 * @param event The name of the event to send 336 * @param extras Any extras included with the event 337 */ 338 public void sendSessionEvent(@NonNull String event, @Nullable Bundle extras) { 339 if (TextUtils.isEmpty(event)) { 340 throw new IllegalArgumentException("event cannot be null or empty"); 341 } 342 try { 343 mBinder.sendEvent(event, extras); 344 } catch (RemoteException e) { 345 Log.wtf(TAG, "Error sending event", e); 346 } 347 } 348 349 /** 350 * This must be called when an app has finished performing playback. If 351 * playback is expected to start again shortly the session can be left open, 352 * but it must be released if your activity or service is being destroyed. 353 */ 354 public void release() { 355 try { 356 mBinder.destroy(); 357 } catch (RemoteException e) { 358 Log.wtf(TAG, "Error releasing session: ", e); 359 } 360 } 361 362 /** 363 * Retrieve a token object that can be used by apps to create a 364 * {@link MediaController} for interacting with this session. The owner of 365 * the session is responsible for deciding how to distribute these tokens. 366 * 367 * @return A token that can be used to create a MediaController for this 368 * session 369 */ 370 public @NonNull Token getSessionToken() { 371 return mSessionToken; 372 } 373 374 /** 375 * Get a controller for this session. This is a convenience method to avoid 376 * having to cache your own controller in process. 377 * 378 * @return A controller for this session. 379 */ 380 public @NonNull MediaController getController() { 381 return mController; 382 } 383 384 /** 385 * Update the current playback state. 386 * 387 * @param state The current state of playback 388 */ 389 public void setPlaybackState(@Nullable PlaybackState state) { 390 mPlaybackState = state; 391 try { 392 mBinder.setPlaybackState(state); 393 } catch (RemoteException e) { 394 Log.wtf(TAG, "Dead object in setPlaybackState.", e); 395 } 396 } 397 398 /** 399 * Update the current metadata. New metadata can be created using 400 * {@link android.media.MediaMetadata.Builder}. 401 * 402 * @param metadata The new metadata 403 */ 404 public void setMetadata(@Nullable MediaMetadata metadata) { 405 try { 406 mBinder.setMetadata(metadata); 407 } catch (RemoteException e) { 408 Log.wtf(TAG, "Dead object in setPlaybackState.", e); 409 } 410 } 411 412 /** 413 * Update the list of items in the play queue. It is an ordered list and 414 * should contain the current item, and previous or upcoming items if they 415 * exist. Specify null if there is no current play queue. 416 * <p> 417 * The queue should be of reasonable size. If the play queue is unbounded 418 * within your app, it is better to send a reasonable amount in a sliding 419 * window instead. 420 * 421 * @param queue A list of items in the play queue. 422 */ 423 public void setQueue(@Nullable List<QueueItem> queue) { 424 try { 425 mBinder.setQueue(new ParceledListSlice<QueueItem>(queue)); 426 } catch (RemoteException e) { 427 Log.wtf("Dead object in setQueue.", e); 428 } 429 } 430 431 /** 432 * Set the title of the play queue. The UI should display this title along 433 * with the play queue itself. 434 * e.g. "Play Queue", "Now Playing", or an album name. 435 * 436 * @param title The title of the play queue. 437 */ 438 public void setQueueTitle(@Nullable CharSequence title) { 439 try { 440 mBinder.setQueueTitle(title); 441 } catch (RemoteException e) { 442 Log.wtf("Dead object in setQueueTitle.", e); 443 } 444 } 445 446 /** 447 * Set some extras that can be associated with the {@link MediaSession}. No assumptions should 448 * be made as to how a {@link MediaController} will handle these extras. 449 * Keys should be fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts. 450 * 451 * @param extras The extras associated with the {@link MediaSession}. 452 */ 453 public void setExtras(@Nullable Bundle extras) { 454 try { 455 mBinder.setExtras(extras); 456 } catch (RemoteException e) { 457 Log.wtf("Dead object in setExtras.", e); 458 } 459 } 460 461 /** 462 * Notify the system that the remote volume changed. 463 * 464 * @param provider The provider that is handling volume changes. 465 * @hide 466 */ 467 public void notifyRemoteVolumeChanged(VolumeProvider provider) { 468 if (provider == null || provider != mVolumeProvider) { 469 Log.w(TAG, "Received update from stale volume provider"); 470 return; 471 } 472 try { 473 mBinder.setCurrentVolume(provider.onGetCurrentVolume()); 474 } catch (RemoteException e) { 475 Log.e(TAG, "Error in notifyVolumeChanged", e); 476 } 477 } 478 479 private void dispatchPlay() { 480 postToCallback(CallbackMessageHandler.MSG_PLAY); 481 } 482 483 private void dispatchPlayFromMediaId(String mediaId, Bundle extras) { 484 postToCallback(CallbackMessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras); 485 } 486 487 private void dispatchPlayFromSearch(String query, Bundle extras) { 488 postToCallback(CallbackMessageHandler.MSG_PLAY_SEARCH, query, extras); 489 } 490 491 private void dispatchSkipToItem(long id) { 492 postToCallback(CallbackMessageHandler.MSG_SKIP_TO_ITEM, id); 493 } 494 495 private void dispatchPause() { 496 postToCallback(CallbackMessageHandler.MSG_PAUSE); 497 } 498 499 private void dispatchStop() { 500 postToCallback(CallbackMessageHandler.MSG_STOP); 501 } 502 503 private void dispatchNext() { 504 postToCallback(CallbackMessageHandler.MSG_NEXT); 505 } 506 507 private void dispatchPrevious() { 508 postToCallback(CallbackMessageHandler.MSG_PREVIOUS); 509 } 510 511 private void dispatchFastForward() { 512 postToCallback(CallbackMessageHandler.MSG_FAST_FORWARD); 513 } 514 515 private void dispatchRewind() { 516 postToCallback(CallbackMessageHandler.MSG_REWIND); 517 } 518 519 private void dispatchSeekTo(long pos) { 520 postToCallback(CallbackMessageHandler.MSG_SEEK_TO, pos); 521 } 522 523 private void dispatchRate(Rating rating) { 524 postToCallback(CallbackMessageHandler.MSG_RATE, rating); 525 } 526 527 private void dispatchCustomAction(String action, Bundle args) { 528 postToCallback(CallbackMessageHandler.MSG_CUSTOM_ACTION, action, args); 529 } 530 531 private void dispatchMediaButton(Intent mediaButtonIntent) { 532 postToCallback(CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent); 533 } 534 535 private void postToCallback(int what) { 536 postToCallback(what, null); 537 } 538 539 private void postCommand(String command, Bundle args, ResultReceiver resultCb) { 540 Command cmd = new Command(command, args, resultCb); 541 postToCallback(CallbackMessageHandler.MSG_COMMAND, cmd); 542 } 543 544 private void postToCallback(int what, Object obj) { 545 postToCallback(what, obj, null); 546 } 547 548 private void postToCallback(int what, Object obj, Bundle extras) { 549 synchronized (mLock) { 550 if (mCallback != null) { 551 mCallback.post(what, obj, extras); 552 } 553 } 554 } 555 556 /** 557 * Return true if this is considered an active playback state. 558 * 559 * @hide 560 */ 561 public static boolean isActiveState(int state) { 562 switch (state) { 563 case PlaybackState.STATE_FAST_FORWARDING: 564 case PlaybackState.STATE_REWINDING: 565 case PlaybackState.STATE_SKIPPING_TO_PREVIOUS: 566 case PlaybackState.STATE_SKIPPING_TO_NEXT: 567 case PlaybackState.STATE_BUFFERING: 568 case PlaybackState.STATE_CONNECTING: 569 case PlaybackState.STATE_PLAYING: 570 return true; 571 } 572 return false; 573 } 574 575 /** 576 * Represents an ongoing session. This may be passed to apps by the session 577 * owner to allow them to create a {@link MediaController} to communicate with 578 * the session. 579 */ 580 public static final class Token implements Parcelable { 581 582 private ISessionController mBinder; 583 584 /** 585 * @hide 586 */ 587 public Token(ISessionController binder) { 588 mBinder = binder; 589 } 590 591 @Override 592 public int describeContents() { 593 return 0; 594 } 595 596 @Override 597 public void writeToParcel(Parcel dest, int flags) { 598 dest.writeStrongBinder(mBinder.asBinder()); 599 } 600 601 @Override 602 public int hashCode() { 603 final int prime = 31; 604 int result = 1; 605 result = prime * result + ((mBinder == null) ? 0 : mBinder.asBinder().hashCode()); 606 return result; 607 } 608 609 @Override 610 public boolean equals(Object obj) { 611 if (this == obj) 612 return true; 613 if (obj == null) 614 return false; 615 if (getClass() != obj.getClass()) 616 return false; 617 Token other = (Token) obj; 618 if (mBinder == null) { 619 if (other.mBinder != null) 620 return false; 621 } else if (!mBinder.asBinder().equals(other.mBinder.asBinder())) 622 return false; 623 return true; 624 } 625 626 ISessionController getBinder() { 627 return mBinder; 628 } 629 630 public static final Parcelable.Creator<Token> CREATOR 631 = new Parcelable.Creator<Token>() { 632 @Override 633 public Token createFromParcel(Parcel in) { 634 return new Token(ISessionController.Stub.asInterface(in.readStrongBinder())); 635 } 636 637 @Override 638 public Token[] newArray(int size) { 639 return new Token[size]; 640 } 641 }; 642 } 643 644 /** 645 * Receives media buttons, transport controls, and commands from controllers 646 * and the system. A callback may be set using {@link #setCallback}. 647 */ 648 public abstract static class Callback { 649 private MediaSession mSession; 650 651 public Callback() { 652 } 653 654 /** 655 * Called when a controller has sent a command to this session. 656 * The owner of the session may handle custom commands but is not 657 * required to. 658 * 659 * @param command The command name. 660 * @param args Optional parameters for the command, may be null. 661 * @param cb A result receiver to which a result may be sent by the command, may be null. 662 */ 663 public void onCommand(@NonNull String command, @Nullable Bundle args, 664 @Nullable ResultReceiver cb) { 665 } 666 667 /** 668 * Called when a media button is pressed and this session has the 669 * highest priority or a controller sends a media button event to the 670 * session. The default behavior will call the relevant method if the 671 * action for it was set. 672 * <p> 673 * The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a 674 * KeyEvent in {@link Intent#EXTRA_KEY_EVENT} 675 * 676 * @param mediaButtonIntent an intent containing the KeyEvent as an 677 * extra 678 */ 679 public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) { 680 if (mSession != null 681 && Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) { 682 KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); 683 if (ke != null && ke.getAction() == KeyEvent.ACTION_DOWN) { 684 PlaybackState state = mSession.mPlaybackState; 685 long validActions = state == null ? 0 : state.getActions(); 686 switch (ke.getKeyCode()) { 687 case KeyEvent.KEYCODE_MEDIA_PLAY: 688 if ((validActions & PlaybackState.ACTION_PLAY) != 0) { 689 onPlay(); 690 } 691 break; 692 case KeyEvent.KEYCODE_MEDIA_PAUSE: 693 if ((validActions & PlaybackState.ACTION_PAUSE) != 0) { 694 onPause(); 695 } 696 break; 697 case KeyEvent.KEYCODE_MEDIA_NEXT: 698 if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) { 699 onSkipToNext(); 700 } 701 break; 702 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 703 if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) { 704 onSkipToPrevious(); 705 } 706 break; 707 case KeyEvent.KEYCODE_MEDIA_STOP: 708 if ((validActions & PlaybackState.ACTION_STOP) != 0) { 709 onStop(); 710 } 711 break; 712 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: 713 if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) { 714 onFastForward(); 715 } 716 break; 717 case KeyEvent.KEYCODE_MEDIA_REWIND: 718 if ((validActions & PlaybackState.ACTION_REWIND) != 0) { 719 onRewind(); 720 } 721 break; 722 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 723 case KeyEvent.KEYCODE_HEADSETHOOK: 724 boolean isPlaying = state == null ? false 725 : state.getState() == PlaybackState.STATE_PLAYING; 726 boolean canPlay = (validActions & (PlaybackState.ACTION_PLAY_PAUSE 727 | PlaybackState.ACTION_PLAY)) != 0; 728 boolean canPause = (validActions & (PlaybackState.ACTION_PLAY_PAUSE 729 | PlaybackState.ACTION_PAUSE)) != 0; 730 if (isPlaying && canPause) { 731 onPause(); 732 } else if (!isPlaying && canPlay) { 733 onPlay(); 734 } 735 break; 736 } 737 } 738 } 739 return false; 740 } 741 742 /** 743 * Override to handle requests to begin playback. 744 */ 745 public void onPlay() { 746 } 747 748 /** 749 * Override to handle requests to play a specific mediaId that was 750 * provided by your app's {@link MediaBrowserService}. 751 */ 752 public void onPlayFromMediaId(String mediaId, Bundle extras) { 753 } 754 755 /** 756 * Override to handle requests to begin playback from a search query. 757 */ 758 public void onPlayFromSearch(String query, Bundle extras) { 759 } 760 761 /** 762 * Override to handle requests to play an item with a given id from the 763 * play queue. 764 */ 765 public void onSkipToQueueItem(long id) { 766 } 767 768 /** 769 * Override to handle requests to pause playback. 770 */ 771 public void onPause() { 772 } 773 774 /** 775 * Override to handle requests to skip to the next media item. 776 */ 777 public void onSkipToNext() { 778 } 779 780 /** 781 * Override to handle requests to skip to the previous media item. 782 */ 783 public void onSkipToPrevious() { 784 } 785 786 /** 787 * Override to handle requests to fast forward. 788 */ 789 public void onFastForward() { 790 } 791 792 /** 793 * Override to handle requests to rewind. 794 */ 795 public void onRewind() { 796 } 797 798 /** 799 * Override to handle requests to stop playback. 800 */ 801 public void onStop() { 802 } 803 804 /** 805 * Override to handle requests to seek to a specific position in ms. 806 * 807 * @param pos New position to move to, in milliseconds. 808 */ 809 public void onSeekTo(long pos) { 810 } 811 812 /** 813 * Override to handle the item being rated. 814 * 815 * @param rating 816 */ 817 public void onSetRating(@NonNull Rating rating) { 818 } 819 820 /** 821 * Called when a {@link MediaController} wants a {@link PlaybackState.CustomAction} to be 822 * performed. 823 * 824 * @param action The action that was originally sent in the 825 * {@link PlaybackState.CustomAction}. 826 * @param extras Optional extras specified by the {@link MediaController}. 827 */ 828 public void onCustomAction(@NonNull String action, @Nullable Bundle extras) { 829 } 830 831 private void setSession(MediaSession session) { 832 mSession = session; 833 } 834 } 835 836 /** 837 * @hide 838 */ 839 public static class CallbackStub extends ISessionCallback.Stub { 840 private WeakReference<MediaSession> mMediaSession; 841 842 public CallbackStub(MediaSession session) { 843 mMediaSession = new WeakReference<MediaSession>(session); 844 } 845 846 @Override 847 public void onCommand(String command, Bundle args, ResultReceiver cb) { 848 MediaSession session = mMediaSession.get(); 849 if (session != null) { 850 session.postCommand(command, args, cb); 851 } 852 } 853 854 @Override 855 public void onMediaButton(Intent mediaButtonIntent, int sequenceNumber, 856 ResultReceiver cb) { 857 MediaSession session = mMediaSession.get(); 858 try { 859 if (session != null) { 860 session.dispatchMediaButton(mediaButtonIntent); 861 } 862 } finally { 863 if (cb != null) { 864 cb.send(sequenceNumber, null); 865 } 866 } 867 } 868 869 @Override 870 public void onPlay() { 871 MediaSession session = mMediaSession.get(); 872 if (session != null) { 873 session.dispatchPlay(); 874 } 875 } 876 877 @Override 878 public void onPlayFromMediaId(String mediaId, Bundle extras) { 879 MediaSession session = mMediaSession.get(); 880 if (session != null) { 881 session.dispatchPlayFromMediaId(mediaId, extras); 882 } 883 } 884 885 @Override 886 public void onPlayFromSearch(String query, Bundle extras) { 887 MediaSession session = mMediaSession.get(); 888 if (session != null) { 889 session.dispatchPlayFromSearch(query, extras); 890 } 891 } 892 893 @Override 894 public void onSkipToTrack(long id) { 895 MediaSession session = mMediaSession.get(); 896 if (session != null) { 897 session.dispatchSkipToItem(id); 898 } 899 } 900 901 @Override 902 public void onPause() { 903 MediaSession session = mMediaSession.get(); 904 if (session != null) { 905 session.dispatchPause(); 906 } 907 } 908 909 @Override 910 public void onStop() { 911 MediaSession session = mMediaSession.get(); 912 if (session != null) { 913 session.dispatchStop(); 914 } 915 } 916 917 @Override 918 public void onNext() { 919 MediaSession session = mMediaSession.get(); 920 if (session != null) { 921 session.dispatchNext(); 922 } 923 } 924 925 @Override 926 public void onPrevious() { 927 MediaSession session = mMediaSession.get(); 928 if (session != null) { 929 session.dispatchPrevious(); 930 } 931 } 932 933 @Override 934 public void onFastForward() { 935 MediaSession session = mMediaSession.get(); 936 if (session != null) { 937 session.dispatchFastForward(); 938 } 939 } 940 941 @Override 942 public void onRewind() { 943 MediaSession session = mMediaSession.get(); 944 if (session != null) { 945 session.dispatchRewind(); 946 } 947 } 948 949 @Override 950 public void onSeekTo(long pos) { 951 MediaSession session = mMediaSession.get(); 952 if (session != null) { 953 session.dispatchSeekTo(pos); 954 } 955 } 956 957 @Override 958 public void onRate(Rating rating) { 959 MediaSession session = mMediaSession.get(); 960 if (session != null) { 961 session.dispatchRate(rating); 962 } 963 } 964 965 @Override 966 public void onCustomAction(String action, Bundle args) { 967 MediaSession session = mMediaSession.get(); 968 if (session != null) { 969 session.dispatchCustomAction(action, args); 970 } 971 } 972 973 @Override 974 public void onAdjustVolume(int direction) { 975 MediaSession session = mMediaSession.get(); 976 if (session != null) { 977 if (session.mVolumeProvider != null) { 978 session.mVolumeProvider.onAdjustVolume(direction); 979 } 980 } 981 } 982 983 @Override 984 public void onSetVolumeTo(int value) { 985 MediaSession session = mMediaSession.get(); 986 if (session != null) { 987 if (session.mVolumeProvider != null) { 988 session.mVolumeProvider.onSetVolumeTo(value); 989 } 990 } 991 } 992 993 } 994 995 /** 996 * A single item that is part of the play queue. It contains a description 997 * of the item and its id in the queue. 998 */ 999 public static final class QueueItem implements Parcelable { 1000 /** 1001 * This id is reserved. No items can be explicitly asigned this id. 1002 */ 1003 public static final int UNKNOWN_ID = -1; 1004 1005 private final MediaDescription mDescription; 1006 private final long mId; 1007 1008 /** 1009 * Create a new {@link MediaSession.QueueItem}. 1010 * 1011 * @param description The {@link MediaDescription} for this item. 1012 * @param id An identifier for this item. It must be unique within the 1013 * play queue and cannot be {@link #UNKNOWN_ID}. 1014 */ 1015 public QueueItem(MediaDescription description, long id) { 1016 if (description == null) { 1017 throw new IllegalArgumentException("Description cannot be null."); 1018 } 1019 if (id == UNKNOWN_ID) { 1020 throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID"); 1021 } 1022 mDescription = description; 1023 mId = id; 1024 } 1025 1026 private QueueItem(Parcel in) { 1027 mDescription = MediaDescription.CREATOR.createFromParcel(in); 1028 mId = in.readLong(); 1029 } 1030 1031 /** 1032 * Get the description for this item. 1033 */ 1034 public MediaDescription getDescription() { 1035 return mDescription; 1036 } 1037 1038 /** 1039 * Get the queue id for this item. 1040 */ 1041 public long getQueueId() { 1042 return mId; 1043 } 1044 1045 @Override 1046 public void writeToParcel(Parcel dest, int flags) { 1047 mDescription.writeToParcel(dest, flags); 1048 dest.writeLong(mId); 1049 } 1050 1051 @Override 1052 public int describeContents() { 1053 return 0; 1054 } 1055 1056 public static final Creator<MediaSession.QueueItem> CREATOR = new Creator<MediaSession.QueueItem>() { 1057 1058 @Override 1059 public MediaSession.QueueItem createFromParcel(Parcel p) { 1060 return new MediaSession.QueueItem(p); 1061 } 1062 1063 @Override 1064 public MediaSession.QueueItem[] newArray(int size) { 1065 return new MediaSession.QueueItem[size]; 1066 } 1067 }; 1068 1069 @Override 1070 public String toString() { 1071 return "MediaSession.QueueItem {" + 1072 "Description=" + mDescription + 1073 ", Id=" + mId + " }"; 1074 } 1075 } 1076 1077 private static final class Command { 1078 public final String command; 1079 public final Bundle extras; 1080 public final ResultReceiver stub; 1081 1082 public Command(String command, Bundle extras, ResultReceiver stub) { 1083 this.command = command; 1084 this.extras = extras; 1085 this.stub = stub; 1086 } 1087 } 1088 1089 private class CallbackMessageHandler extends Handler { 1090 1091 private static final int MSG_PLAY = 1; 1092 private static final int MSG_PLAY_MEDIA_ID = 2; 1093 private static final int MSG_PLAY_SEARCH = 3; 1094 private static final int MSG_SKIP_TO_ITEM = 4; 1095 private static final int MSG_PAUSE = 5; 1096 private static final int MSG_STOP = 6; 1097 private static final int MSG_NEXT = 7; 1098 private static final int MSG_PREVIOUS = 8; 1099 private static final int MSG_FAST_FORWARD = 9; 1100 private static final int MSG_REWIND = 10; 1101 private static final int MSG_SEEK_TO = 11; 1102 private static final int MSG_RATE = 12; 1103 private static final int MSG_CUSTOM_ACTION = 13; 1104 private static final int MSG_MEDIA_BUTTON = 14; 1105 private static final int MSG_COMMAND = 15; 1106 1107 private MediaSession.Callback mCallback; 1108 1109 public CallbackMessageHandler(Looper looper, MediaSession.Callback callback) { 1110 super(looper, null, true); 1111 mCallback = callback; 1112 } 1113 1114 public void post(int what, Object obj, Bundle bundle) { 1115 Message msg = obtainMessage(what, obj); 1116 msg.setData(bundle); 1117 msg.sendToTarget(); 1118 } 1119 1120 public void post(int what, Object obj) { 1121 obtainMessage(what, obj).sendToTarget(); 1122 } 1123 1124 public void post(int what) { 1125 post(what, null); 1126 } 1127 1128 public void post(int what, Object obj, int arg1) { 1129 obtainMessage(what, arg1, 0, obj).sendToTarget(); 1130 } 1131 1132 @Override 1133 public void handleMessage(Message msg) { 1134 switch (msg.what) { 1135 case MSG_PLAY: 1136 mCallback.onPlay(); 1137 break; 1138 case MSG_PLAY_MEDIA_ID: 1139 mCallback.onPlayFromMediaId((String) msg.obj, msg.getData()); 1140 break; 1141 case MSG_PLAY_SEARCH: 1142 mCallback.onPlayFromSearch((String) msg.obj, msg.getData()); 1143 break; 1144 case MSG_SKIP_TO_ITEM: 1145 mCallback.onSkipToQueueItem((Long) msg.obj); 1146 case MSG_PAUSE: 1147 mCallback.onPause(); 1148 break; 1149 case MSG_STOP: 1150 mCallback.onStop(); 1151 break; 1152 case MSG_NEXT: 1153 mCallback.onSkipToNext(); 1154 break; 1155 case MSG_PREVIOUS: 1156 mCallback.onSkipToPrevious(); 1157 break; 1158 case MSG_FAST_FORWARD: 1159 mCallback.onFastForward(); 1160 break; 1161 case MSG_REWIND: 1162 mCallback.onRewind(); 1163 break; 1164 case MSG_SEEK_TO: 1165 mCallback.onSeekTo((Long) msg.obj); 1166 break; 1167 case MSG_RATE: 1168 mCallback.onSetRating((Rating) msg.obj); 1169 break; 1170 case MSG_CUSTOM_ACTION: 1171 mCallback.onCustomAction((String) msg.obj, msg.getData()); 1172 break; 1173 case MSG_MEDIA_BUTTON: 1174 mCallback.onMediaButtonEvent((Intent) msg.obj); 1175 break; 1176 case MSG_COMMAND: 1177 Command cmd = (Command) msg.obj; 1178 mCallback.onCommand(cmd.command, cmd.extras, cmd.stub); 1179 break; 1180 } 1181 } 1182 } 1183} 1184