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