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