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