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